Microsoft Agent Framework Go

Open-source contributions to microsoft/agent-framework-go — the official Go implementation of the Microsoft Agent Framework for building AI agents and multi-agent workflows. My work targets runtime correctness and safety hardening: concurrency and data-race fixes, nil-safety and panic guards across the model providers, streaming-protocol correctness, and request-path validation — each landed with a regression test that fails before the change and passes after.

202 Pull Requests · 99 Merged · 100 Open · 3 Closed · Go

About the framework

The Microsoft Agent Framework is Microsoft's open-source framework for building AI agents and orchestrating multi-agent workflows, with a provider layer spanning OpenAI, Anthropic, Azure OpenAI, and Azure AI Foundry. agent-framework-go is its Go implementation. My contributions span the agent runtime and middleware (tool approval, agent mode, skills), the workflow routing engine, and the model providers — the kind of concurrency- and stream-sensitive code where a missing guard surfaces only under real parallel tool use.

The two authored reference implementations on my portfolio — Genie Project (multi-agent financial assistant) and Bodh (medical diagnostic panel) — are built on this framework, which is what surfaced these upstream issues in production.

Merged

Accepted upstream into main — runtime-correctness, concurrency, and provider-parity fixes, each landed with a regression test that fails before the change and passes after.

#707 · agent/harness/agentmode · Merged Jul 29, 2026 · +44 −5

Two harness context-provider default prompts had drifted from the current .NET source. Because both ship as the runtime system prompt via WithInstructions, the stale text changed agent behavior relative to .NET. This copies the reworked TodoProvider and AgentModeProvider execute-mode strings verbatim, restoring cross-SDK prompt parity.

#Go #Middleware #Regression test
#650 · provider/copilotprovider · Merged Jul 29, 2026 · +114 −0

responseUpdateForSessionEvent had no case for the Copilot reasoning or reasoning-delta events, so both fell through and were emitted as empty RawContent, while the non-streaming path dropped the reasoning text entirely. The fix emits proper TextReasoningContent for both streaming events and the non-streaming message, carrying protected reasoning data.

#Go #Copilot #Providers #Streaming
#687 · cmd/verifyexamples · Merged Jul 29, 2026 · +366 −0

Nothing under examples/ showed a parent workflow answering a nested subworkflow's request locally rather than always bubbling it to the top level. This adds a deterministic offline example with auto-approved and escalated paths, plus a black-box test and a verifyexamples registration, mirroring the .NET/Python request-interception samples.

#Go #Workflow #Examples #Regression test
#728 · message · Merged Jul 29, 2026 · +2 −0

Two exported accessors, Contents.Usage() (which sums the UsageDetails of every UsageContent) and UsageDetails.Add() (which accumulates token counts and merges AdditionalCounts), lacked doc comments while their sibling Contents.Text already had one. This documents their aggregate-and-merge semantics so the public API stays discoverable via godoc and aligned with the cross-SDK usage helpers.

#Go #Usage #Docs
#716 · cmd/verifyexamples · Merged Jul 29, 2026 · +339 −0

Every existing checkpoint example used the in-memory manager, leaving the durable NewFileSystemJSONStore plus NewJSONManager backend exercised only by unit tests. This adds a two-phase example that persists checkpoints to disk, closes the store to simulate an interruption, then reopens the directory and resumes the latest checkpoint via RetrieveIndex.

#Go #Workflow #Checkpoint #Examples
#617 · provider/foundryprovider · Merged Jul 29, 2026 · +21 −1

toResponseItem selected the message role correctly but always emitted the content part as input_text, so an assistant memory item was serialized as user input rather than model output. The fix chooses the content-part type by role, aligning Foundry memory items with the Responses input/output convention used elsewhere in the repo.

#Go #Foundry #Providers
#741 · message · Merged Jul 29, 2026 · +35 −2

AlwaysApproveToolResponse and AlwaysApproveToolWithArgumentsResponse copied the request's AdditionalProperties map by reference, so the request and emitted response shared one instance and later mutations of the request leaked into the response. This switches both to maps.Clone for an independent snapshot, matching CreateResponse and the response-is-a-snapshot contract across SDKs.

#Go #Data race #Middleware #Regression test
#629 · provider/anthropicprovider · Merged Jul 29, 2026 · +37 −3

The Anthropic streaming path unconditionally yielded a terminal UsageContent update and only then checked stream.Err(), so an auth failure, mid-stream drop, or context cancel still emitted phantom or partial usage before surfacing the error. The fix reorders handling to surface the error first and emit usage only on successful completion, matching the OpenAI provider.

#Go #Anthropic #Streaming #Usage
#714 · provider/copilotprovider · Merged Jul 29, 2026 · +46 −0

The provider mapped every SessionErrorData to a terminal error, so transient rate limits the runtime was about to auto-recover from surfaced to the caller as fatal. This dereferences EligibleForAutoSwitch: when set on a rate_limit error it surfaces the error as a non-terminal notification and keeps pumping the auto-switch events to the eventual idle completion.

#Go #Providers #Copilot #Nil-safety
#698 · provider/copilotprovider · Merged Jul 29, 2026 · +44 −0

The Copilot provider only ever set Result and left Error nil on a tool.execution_complete with success == false, so a failed tool call looked identical to a success to any consumer inspecting Error. The fix populates FunctionResultContent.Error from the SDK message and code, restoring the canonical failure channel and .NET/Python semantics.

#Go #Copilot #Regression test
#725 · provider/aguiprovider · Merged Jul 29, 2026 · +49 −0

onEvent had no case for MessagesSnapshotEvent, so every MESSAGES_SNAPSHOT frame — a first-class event that re-syncs full conversation history — hit the default branch and was silently discarded, losing the continuity signal. This emits an assistant ResponseUpdate exposing the snapshot under AdditionalProperties["agui_messages_snapshot"], matching how other AG-UI metadata is surfaced.

#Go #Providers #AG-UI #Observability
#721 · tool/mcptool · Merged Jul 29, 2026 · +49 −0

The type switch handled Content and []Content but not the named slice message.Contents that Message.Contents actually is, so results fell through to the default and were JSON-marshaled into one TextContent — collapsing blocks, base64-encoding images, and dropping the error flag. This adds a case message.Contents delegating to the correct per-block branch.

#Go #MCP #Reflection #Regression test
#737 · provider/openaiprovider · Merged Jul 29, 2026 · +17 −55

addUsage unconditionally allocated AdditionalCounts and wrote four audio/prediction token details, so every text-only response carried four zero-valued entries. That made otelprovider's hasUsage return true and emit zero-valued span attributes. This guards each key so it is added only when non-zero, leaving the map nil in the common case, matching the Anthropic provider and Python SDK.

#Go #OpenAI #Usage #Observability
#713 · provider/a2aprovider · Merged Jul 29, 2026 · +61 −0

The A2A client built its SendMessageRequest with no Config, so a caller passing AllowBackgroundResponses(true) passed generic validation but was silently ignored — the request never asked the remote agent to return immediately. This reads the option and sets SendMessageConfig.ReturnImmediately on the non-streaming send, matching .NET and the OpenAI provider.

#Go #Providers #A2A #Regression test
#706 · workflow · Merged Jul 29, 2026 · +70 −0

Listing the same target twice in one switch case, as in AddCase(pred, t1, t1), appended duplicate indices with no membership check, and nothing downstream deduplicated, so the matched message reached that executor twice. This tracks a per-call seen set so each target is emitted at most once, restoring exactly-once delivery per distinct target as in .NET.

#Go #Workflow #Router #Regression test
#654 · provider/openaiprovider · Merged Jul 29, 2026 · +56 −1

The non-streaming Chat Completions path mapped a model refusal to an ErrorContent but left ErrorCode empty, whereas every Responses-path refusal tags ErrorCode: "Refusal". A consumer routing on the code would classify Responses refusals but drop identical chat refusals into the empty bucket. The fix applies the same "Refusal" tag on the chat path.

#Go #OpenAI #Providers
#701 · provider/openaiprovider · Merged Jul 29, 2026 · +77 −1

The provider decided a hosted MCPServer's address type from url.Parse's error, but a bare connector ID like connector_googledrive parses without error and fell into the server_url branch, which the API rejects as it is format:uri and mutually exclusive with connector_id. The fix discriminates on an http/https scheme, routing bare IDs to connector_id.

#Go #OpenAI #MCP #Regression test
#705 · agent · Merged Jul 29, 2026 · +71 −9

Response.ToUpdates() copied four response-level fields onto emitted updates but omitted ContinuationToken, so a response carrying a token round-tripped through ToUpdates then Collect came back empty. This extends the trailing extra-update to fire on a non-empty token and set it, keeping the conversion lossless and matching .NET's AsChatResponseUpdate.

#Go #Streaming #Usage #Regression test
#630 · agent/harness/toolautocall · Merged Jul 29, 2026 · +67 −11

When the auto tool-call loop reconstructed the assistant turn preceding tool results, it rebuilt it solely from FunctionCallContents, so any TextContent and TextReasoningContent the model emitted alongside the call were yielded to the caller but dropped before the next provider iteration. The fix carries the assistant's text and reasoning over in natural order.

#Go #Middleware #Streaming
#697 · workflow/internal/observability · Merged Jul 29, 2026 · +96 −4

Workflow spans set error.type via reflect.TypeOf(err).String(), yielding qualified names like *errors.errorString, while every other site in the repo used otelx.ErrorTypeName for the short unqualified name. This swaps the three workflow sites to ErrorTypeName, fixing an intra-repo inconsistency and a parity break with Python.

#Go #Observability #Reflection #Regression test
#693 · provider/geminiprovider · Merged Jul 29, 2026 · +85 −0

Gemini 3 attaches thought_signature to the same part that carries the function_call, but buildResponsePart read it only inside the Thought branch, so it was silently dropped and could never be replayed. The fix emits a TextReasoningContent with the base64 signature before the FunctionCallContent, closing the multi-turn tool round-trip like the Python reference.

#Go #Gemini #Regression test
#743 · provider/geminiprovider · Merged Jul 29, 2026 · +108 −0

When Gemini blocks a prompt for a safety policy it returns zero candidates and sets PromptFeedback.BlockReason, but the provider gated all extraction behind a candidate check and never inspected that field, so a blocked prompt produced a successful, empty response. This appends an ErrorContent carrying the block reason and message in both the non-streaming and streaming paths so callers can distinguish a block from an empty completion.

#Go #Gemini #Streaming #Regression test
#723 · provider/openaiprovider · Merged Jul 29, 2026 · +97 −6

The Responses API only populates a code_interpreter_call's outputs (execution logs and images) when code_interpreter_call.outputs is requested via Include, but the builder mapped the tool without asking for it, so out.Outputs was always empty and the feature was effectively dead. This appends the include, duplicate-guarded, matching the .NET and Python SDKs.

#Go #Providers #OpenAI #Streaming
#715 · provider/aguiprovider · Merged Jul 29, 2026 · +102 −0

The AG-UI onEvent switch had no case for TextMessageChunkEvent, so any server streaming assistant text via TEXT_MESSAGE_CHUNK had every chunk fall through to the default and be silently discarded — all response content lost. This adds a case mirroring the TEXT_MESSAGE_CONTENT handling, mapping the chunk delta to the same TextContent update.

#Go #Providers #AG-UI #Streaming
#619 · provider/foundryprovider · Merged Jul 29, 2026 · +103 −0

The Foundry MemoryProvider exposed only Invoking/Invoked, and MemoryStoresClient.DeleteScope lived in an internal package, so external callers had no way to clear a scope's stored memories. This adds an exported EnsureStoredMemoriesDeleted that resolves the scope and deletes it, treating a 404 as success, so applications can honor per-scope data-deletion requests.

#Go #Foundry #Providers
#681 · provider/anthropicprovider · Merged Jul 29, 2026 · +98 −12

The Anthropic streaming loop had no citations_delta handling, so streamed text arrived without the CitationAnnotation values the non-streaming path produces. The fix inspects the accumulated text block on content_block_stop and emits an annotations-only TextContent, sharing a citationAnnotations helper so streamed and non-streamed responses carry identical citation metadata.

#Go #Anthropic #Streaming #Regression test
#655 · provider/otelprovider · Merged Jul 29, 2026 · +114 −8

The GenAI invoke_agent span was started with the raw agent name rather than the required operation target form, and gen_ai.agent.name/gen_ai.agent.description were set unconditionally, emitting empty-string attributes for unnamed agents. The fix names the span like the sibling tool span and appends those attributes only when non-empty, matching the .NET and Python conventions.

#Go #Observability #Providers
#626 · message · Merged Jul 29, 2026 · +116 −6

Annotation and region unmarshalling used the no-fallback discriminated-union variant, so any unrecognized subtype failed the entire enclosing message deserialization. The fix switches to the fallback variant with internal RawAnnotation/RawAnnotatedRegion types that preserve and round-trip the original JSON, matching how RawContent already tolerates unknown content kinds.

#Go #Reflection #Regression test
#709 · cmd/verifyexamples · Merged Jul 29, 2026 · +132 −0

WithChainOnlyAgentResponses(true) had no example coverage, though it controls whether each agent forwards only its own reply or the accumulated conversation. This adds a runnable, deterministic writer-translator-reviewer chain that prints the messages each stage receives, making the message-forwarding semantics visible, and registers it in verifyexamples.

#Go #Workflow #Examples
#703 · cmd/verifyexamples · Merged Jul 29, 2026 · +127 −0

ConcurrentWorkflowBuilder.WithAggregator and the MessageAggregator type are public API, but no example exercised them. This adds a runnable sample that fans a prompt to several domain-expert agents and folds their responses into one summary via a custom aggregator, registered in verifyexamples, mirroring the .NET/Python ConcurrentBuilder aggregator samples.

#Go #Workflow #Concurrency #Examples
#683 · provider/openaiprovider · Merged Jul 29, 2026 · +136 −0

The streaming response.output_item.done switch had no case for reasoning items, so a completed reasoning item fell through to a default that discarded its EncryptedContent, breaking encrypted-reasoning replay when store=false. The fix adds a reasoning-item case that emits TextReasoningContent with ProtectedData, mirroring the non-streaming handler.

#Go #OpenAI #Streaming #Regression test
#710 · provider/copilotprovider · Merged Jul 29, 2026 · +124 −19

The first turn cloned the full SessionConfig, but resumed turns hand-copied only 18 fields, silently dropping the rest, so a multi-turn agent's configured options quietly stopped taking effect after turn one. This extends copyResumeSessionConfig to carry over every field shared with ResumeSessionConfig, mirroring the create path and the SDK's own resume request.

#Go #Providers #Copilot #Regression test
#648 · provider/a2aprovider · Merged Jul 29, 2026 · +159 −2

The A2A converter dropped each artifact's own Metadata: yieldTask forwarded only task-level metadata, and the streaming artifact-update case read event-level metadata instead of the artifact's. The fix folds artifact metadata into the update's AdditionalProperties via a mergeMetadata helper with defined precedence, so artifact-level extension metadata reaches consumers.

#Go #A2A #Providers #Streaming
#700 · provider/openaiprovider · Merged Jul 29, 2026 · +161 −1

The Chat Completions conversion built TextContent only from message content and never read choice.Message.Annotations, so web-search url_citation annotations were dropped even though the tool was enabled. A new populateChatAnnotations helper maps them onto Annotations, restoring parity with the Responses path and the .NET/Python SDKs.

#Go #OpenAI #Providers #Regression test
#647 · provider/aguiprovider · Merged Jul 29, 2026 · +163 −2

On a mid-stream failure the AG-UI host had already built a proper RUN_ERROR event, but streamEvents ignored it and wrote a non-standard CUSTOM frame carrying no runId. The client accumulator had no case for it, so the error was silently swallowed. The fix prefers the yielded RUN_ERROR event so consumers observe the failure.

#Go #AG-UI #Providers #Streaming
#732 · cmd/verifyexamples · Merged Jul 29, 2026 · +170 −0

The OpenAI chat path maps audio DataContent to input_audio parts and other files (e.g. application/pdf) to file parts, but every existing multimodal example only used images, leaving those branches without runnable coverage. This adds a step13_using_audio_and_files example sending a WAV and a PDF, plus a black-box regression test for both encoding branches.

#Go #OpenAI #Examples #Regression test
#726 · cmd/verifyexamples · Merged Jul 29, 2026 · +172 −0

mcp.CommandTransport (subprocess over stdin/stdout) existed in the pinned go-sdk but had no example — every client call site used remote HTTP transport. This adds a sample that launches a sibling stdio MCP server as a child process, connects via mcptool.Connect, discovers and attaches its tools to a Foundry agent, closing a cross-SDK parity gap.

#Go #MCP #Foundry #Examples
#660 · cmd/verifyexamples · Merged Jul 29, 2026 · +189 −0

The agent/harness/loop middleware was fully wired but had no example. This adds step19_loop_reinvocation, demonstrating both the CompletionMarkerEvaluator with FreshContextPerIteration and a custom EvaluatorFunc returning continue/stop with a MaxIterations cap. It uses a deterministic scripted provider so it runs without credentials and is verified in cmd/verifyexamples.

#Go #Examples #Middleware #Workflow
#658 · message · Merged Jul 29, 2026 · +224 −0

message had MCPServerToolCallContent but no result counterpart, so a completed hosted MCP call carrying output and error was silently dropped by both the non-streaming and streaming Responses paths. This adds MCPServerToolResultContent and handles the mcp_call output item, emitting the call plus its result and surfacing errors, mirroring the code-interpreter pair.

#Go #MCP #OpenAI #Providers
#664 · provider/openaiprovider · Merged Jul 29, 2026 · +228 −8

On the Responses path, the input-image builder set only ImageURL and Detail and never read a file_id from AdditionalProperties, so an image referenced by an already-uploaded file was silently dropped. This adds an imageFileID helper and sets FileID for both URI and data content branches, closing the parity gap with the Python and .NET SDKs.

#Go #Providers #OpenAI #Regression test
#727 · agent · Merged Jul 29, 2026 · +2 −0

MiddlewareFunc.Run was the only exported member of the middleware surface without a doc comment, though the Middleware interface, its Run contract, and the MiddlewareFunc type were all documented. This adds a comment describing the adapter that lets func-based middleware satisfy the interface, mirroring the .NET and Python SDKs so inline-middleware authors get godoc hints.

#Go #Middleware #Docs
#682 · message · Merged Jul 29, 2026 · +2 −0

CodeInterpreterToolCallContent and CodeInterpreterToolResultContent were the only exported content types in content.go without a doc comment, so go doc rendered no description for them. This adds one-line comments mirroring the neighboring FunctionCallContent style, restoring documentation coverage and aligning with the .NET and Python SDKs.

#Go #Docs #MCP
#720 · message · Merged Jul 29, 2026 · +3 −0

The exported TopLevelMediaType methods on DataContent, HostedFileContent, and URIContent had no doc comment, even though the returned value is used behaviorally in comparisons like a.TopLevelMediaType() == "text". This adds a uniform one-line comment across all three, bringing godoc coverage in line with the rest of the content API.

#Go #Docs #Providers
#704 · message · Merged Jul 29, 2026 · +3 −0

The URIContent type was documented but its exported NewURIContent constructor was not, leaving an undocumented outlier. The constructor's behavior is non-obvious: it validates the URI, infers the media type when mediaType is empty, otherwise validates the supplied type, and errors on invalid input. This adds a doc comment describing that contract, matching the .NET and Python SDKs.

#Go #Docs #Providers
#695 · workflow · Merged Jul 29, 2026 · +3 −0

ErrInvalidInputType is a sentinel meant to be matched with errors.Is, wrapped in the inproc runner and execution paths, yet carried no godoc signaling that contract. This documents when it fires — an enqueued input whose type the start executor does not accept — and that callers can match it, matching sentinel-error conventions across the SDKs.

#Go #Docs #Workflow
#734 · message · Merged Jul 29, 2026 · +4 −0

The exported ToolApprovalRequestContent.CreateResponse factory, used in the tool-approval flow, had no doc comment. This documents that it builds an approving or rejecting ToolApprovalResponseContent, carries over the RequestID, and clones the pending call plus header properties — while noting that RawRepresentation is copied by reference, so the result is not a full deep copy.

#Go #Middleware #Docs
#711 · workflow · Merged Jul 29, 2026 · +5 −0

The exported (*PortableValue).As accessor was the only undocumented method among its neighbors, while siblings Is and Delayed and the free function PortableValueAs all carried godoc. This adds a comment describing it as the comma-ok extraction counterpart to Is, coercing the contained value to a given type, keeping the exported surface consistent.

#Go #Workflow #Docs
#699 · message · Merged Jul 29, 2026 · +6 −0

The Annotations and AnnotatedRegions slice wrappers were bare while their element and concrete types were documented, and AnnotatedRegions appears directly in the public API as a CitationAnnotation field. This adds comments describing each type's discriminated-union UnmarshalJSON, matching the polymorphic annotation contracts in the .NET and Python SDKs.

#Go #Docs
#692 · workflow · Merged Jul 29, 2026 · +7 −0

Three workflow.Builder methods — WithName, WithDescription, and WithOutputFrom — lacked doc comments while their siblings were documented. This fills the gap so the builder's fluent API has complete, consistent godoc, matching the name/description and terminal-output concepts documented in the .NET and Python SDKs.

#Go #Docs #Workflow
#690 · tool/functool · Merged Jul 29, 2026 · +7 −0

New and MustNew were the only undocumented exported symbols in func.go, despite New being the primary entry point for constructing function tools. This adds Go-convention doc comments describing schema derivation, argument validation, and the panic-on-error wrapper, completing the package's public godoc.

#Go #Docs #Middleware
#712 · workflow · Merged Jul 29, 2026 · +13 −0

Four core workflow.Builder methods — BindExecutor, AddEdge, AddDirectEdge, and Build — lacked doc comments while their siblings were documented. This fills the gap, spelling out AddDirectEdge's subtle idempotent-versus-error and conditionless-only dedup semantics so callers need not infer them from source, aligning with the .NET/Python WorkflowBuilder.

#Go #Workflow #Docs
#691 · message/messageworkflow · Merged Jul 29, 2026 · +25 −6

The messageworkflow package documented only Configure, leaving the package comment, Options, MessageState, NewMessageState, ProcessTurnMessages, and Reset bare. This adds godoc across the exported surface, noting required options and checkpoint-restore behavior, bringing it in line with sibling packages and the .NET/Python types.

#Go #Docs #Workflow #Checkpoint
#684 · tool/hostedtool · Merged Jul 29, 2026 · +15 −5

MCPServer was the only marker type in tool/hostedtool without a doc comment, so go doc rendered it with no description while its siblings WebSearch, FileSearch, and CodeInterpreter were all documented. This adds a type comment plus per-field comments, describing the hosted MCP marker consistent with the .NET and Python SDKs.

#Go #Docs #MCP
#663 · workflow/inproc · Merged Jul 29, 2026 · +49 −1

A copy-paste error left the Checkpoints doc comment sitting above IsCheckpointingEnabled, describing the wrong method. This restores each comment to its own method and adds godoc to the previously undocumented exported Run, StreamingRun, RunStatus, and ExecutionOption types and their methods, keeping the in-process runner surface consistent.

#Go #Workflow #Checkpoint #Docs
#645 · message · Merged Jul 29, 2026 · +32 −1

The doc comment on Contents.Text() claimed it returned the first text content, but the implementation loops over every element and concatenates all TextContent values with no separator. Since that concatenation matches the intended .NET ChatMessage.Text semantics, only the stale comment was corrected; the code is unchanged.

#Go #Docs #Regression test
#740 · tool/shelltool · Merged Jul 28, 2026 · +5 −0

The five methods that make shelltool.Local usable as an agent tool — Name, Description, Schema, ReturnSchema, and Call — were the only undocumented part of the type while all other exported symbols carried comments. This adds doc comments so the full tool.Tool interface surface is documented, consistent with the .NET/Python shell-tool ports.

#Go #Docs #MCP
#748 · workflow · Merged Jul 28, 2026 · +1 −0

EdgeConnection.Equal was the only undocumented member of the workflow edge-equality API, while sibling Edge.Equal and the EdgeConnection type already carried comments. This documents that equality is slices.Equal over the ordered SourceIDs and SinkIDs, matching the .NET/Python graph model's edge-connection semantics.

#Go #Workflow #Docs
#747 · message · Merged Jul 28, 2026 · +2 −1

The doc comment on message.New claimed it creates a message "with the given role and contents", but its signature is func New(contents ...Content) *Message — it takes no role argument and always hardcodes RoleUser. This rewords the comment to describe the real behavior and points callers at the Role field when a different role is needed.

#Go #Docs #Nil-safety
#749 · agent · Merged Jul 28, 2026 · +4 −0

Four exported accessors sat next to documented neighbors but had no comments: Response.String(), Response.Usage(), Response.Update(), and ResponseUpdate.Usage(). This documents each — concatenated text, summed token usage, folding a streaming update into the response, and per-update usage — closing the gap in go doc ./agent without any behavior change.

#Go #Streaming #Usage #Docs
#751 · message · Merged Jul 28, 2026 · +206 −6

The URIContent arm of Gemini's buildRequestParts emitted every URI as FileData.FileURI, but a valid data: URI is not an external file reference, so Gemini ignored it and the multimodal input was silently dropped. This branches on scheme, decoding data: URIs to InlineData via a new message.DecodeDataURI helper while keeping gs:///http(s) on the FileData path, matching fixes already made for the other providers.

#Go #Gemini #Providers #Regression test
#752 · cmd/verifyexamples · Merged Jul 28, 2026 · +168 −0

The public messageworkflow turn-token / message-accumulation protocol had no example, leaving that path undocumented for users. This adds a deterministic driver->relay->chat->collector example demonstrating Configure, ConfigureForwarding, and DisableAutoSendTurnToken, showing a TurnToken triggers exactly one turn regardless of accumulated messages, mirroring the .NET/Python samples.

#Go #Workflow #Examples
#753 · provider/foundryprovider · Merged Jul 28, 2026 · +4 −0

The two MemoryProvider runtime hooks that satisfy agent.ContextProvider were undocumented while every other exported symbol in the file was. This documents Invoking (searches the Foundry memory store for relevant context before a run) and Invoked (persists request and response messages after a run), keeping the surface aligned with the .NET/Python context providers.

#Go #Foundry #Docs
#754 · agent/harness/agentmode · Merged Jul 28, 2026 · +9 −0

The Invoking and Invoked pass-through methods on todo.Provider, agentmode.Provider, and shelltool.EnvironmentProvider — the exact surface satisfying agent.ContextProvider — carried no godoc while their neighboring methods did. This adds one-line comments to all six, closing the documentation gap across the three providers.

#Go #Docs #Middleware
#755 · tool/mcptool · Merged Jul 28, 2026 · +29 −2

The mcptool package doc advertised connecting to MCP servers via "stdio, HTTP, or WebSocket", but the pinned go-sdk provides no WebSocket transport, so a reader would hunt for one that cannot be constructed. This narrows the wording to stdio (subprocess) and HTTP (SSE / streamable HTTP) and adds a test scanning the sources that fails if "websocket" reappears.

#Go #MCP #Docs #Regression test
#596 · provider/anthropicprovider · Merged Jul 27, 2026 · +149 −3

The Anthropic provider never populated FinishReason, so collected responses carried an empty value even though the Messages API returns stop_reason on both the response and the streaming message_delta. A new mapStopReason helper maps SDK stop reasons to the canonical finish-reason values on both paths, bringing Anthropic to parity with the OpenAI and Copilot providers.

#Go #Anthropic #Providers #Streaming
#606 · provider/a2aprovider · Merged Jul 27, 2026 · +87 −1

When an A2A response carried an empty context ID, the mismatch guard wrongly fired and setContextID clobbered the stored ID with an empty string, erroring streaming runs that legitimately send a bare message. The fix adds a non-empty check to the guard and an early return in setContextID, restoring symmetry with setTaskID.

#Go #A2A #Providers #Streaming
#614 · provider/openaiprovider · Merged Jul 29, 2026 · +114 −0

populateAnnotations mapped only a fraction of the Responses SDK annotation data: URL citations dropped title and start/end indices, file citations dropped the filename, and container-file-citation and file-path annotations were unhandled. The fix copies titles, builds text-span annotated regions, and adds the missing annotation cases so citation fidelity is no longer silently discarded.

#Go #OpenAI #Providers
#668 · agent · Merged Jul 26, 2026 · +116 −14

(*Response).Update overwrote an already-set CreatedAt whenever a later streamed chunk carried a strictly-later timestamp, so a collected message was stamped with the last chunk's time instead of the first. The fix sets CreatedAt only when currently zero and valid, adding an isValidCreatedAt helper so epoch-zero counts as unset, matching .NET's first-valid-wins behavior.

#Go #Streaming #Usage #Regression test
#651 · agent · Merged Jul 26, 2026 · +44 −1

When a downstream consumer abandoned the range early, contextProviderMiddleware.Run still ran Invoked and, on error, called yield again, violating the iter.Seq2 contract and triggering Go's runtime panic for continued iteration. The fix tracks a stopped flag and guards the post-loop error yield, keeping the store side-effect while suppressing the illegal yield.

#Go #Middleware #Panic guard #Concurrency
#729 · provider/geminiprovider · Merged Jul 26, 2026 · +174 −6

The Gemini tool loop only handled tool.FuncTool, so hosted tools attached via agent.WithTool fell through as a silent no-op. This maps WebSearch, CodeInterpreter, and FileSearch onto their native genai.Tool fields (GoogleSearch, CodeExecution, FileSearch), each appended as its own entry, bringing Gemini to parity with the OpenAI provider's hosted-tool forwarding.

#Go #Gemini #Providers #Regression test
#527 · tool/functool · Merged Jul 24, 2026 · +29 −1

inputFormatFor wrapped any input whose Kind was not reflect.Struct, so a pointer-to-struct handler (Kind == reflect.Ptr) got a nested {"Arg0":{...}} schema and rejected the flat arguments a value receiver accepts. This dereferences pointers before the struct check, so *Struct and Struct handlers produce the same flat schema, matching jsonformat.ForType.

#Go #Reflection #Nil-safety #Regression test
#718 · provider/anthropicprovider · Merged Jul 24, 2026 · +148 −0

The Anthropic Messages and OpenAI Responses streaming paths iterated the SSE stream but never called Close(), so an early consumer break or context cancellation returned without releasing the HTTP response body — leaking the underlying connection. This adds defer stream.Close() after each streaming call, matching the Chat Completions path and the .NET/Python SDKs.

#Go #Providers #Streaming #Anthropic
#661 · workflow · Merged Jul 23, 2026 · +8 −0

Four exported symbols in the workflow package lacked doc comments while every sibling event already carried one — RequestHaltEvent was the lone undocumented event, and Builder/NewBuilder are the primary workflow-construction entry points. This adds Go-convention doc comments so each renders correctly under go doc, bringing the port to parity with the .NET and Python SDKs.

#Go #Workflow #Docs
#607 · agent · Merged Jul 23, 2026 · +59 −1

The structured-output middleware accumulated provider updates to assemble the JSON payload but never yielded any downstream, so agent.invoke built an empty historyResponse — the collected Response had empty text and the history provider persisted an empty assistant message even though the value deserialized correctly. The fix forwards each update via yield before the final unmarshal, surfacing the assistant response.

#Go #Middleware #Streaming #Regression test
#556 · provider/openaiprovider · Merged Jul 23, 2026 · +47 −0

The non-streaming Chat Completions path surfaced a model refusal as ErrorContent, but the streaming loop only inspected tool calls, content deltas, usage, and finish reason — never the accumulator's JustFinishedRefusal(). A refused streamed request degraded to a silent empty assistant message with no indication of why. This reads the accumulated refusal and emits it as ErrorContent, closing the streaming-vs-non-streaming parity gap.

#Go #Providers #OpenAI #Streaming
#526 · workflow/internal/checkpoint · Merged Jul 23, 2026 · +34 −0

Checkpoint.UnmarshalJSON rebuilt StateData with a hasher using a zero-value maphash.Hash, which picks a fresh random seed per call — so the same ScopeKey hashed differently every time, breaking Load/Delete on restored state and leaving Equal shared-scope keys uncollapsed so restore was non-deterministic. The fix sets a fixed process-wide seed, mirroring the sibling hasher in state.go.

#Go #Workflow #Checkpoint #Regression test
#620 · workflow/agentworkflow · Merged Jul 23, 2026 · +26 −1

When a GroupChatManager.SelectNextAgent returned an agent outside the workflow's participants, handleTurn returned an error that propagated as an ErrorEvent and aborted the run, discarding the accumulated conversation. This changes the map-miss branch to host.complete(ctx) so a non-participant selection ends the chat gracefully and yields the conversation, matching .NET and the existing nil-selection case.

#Go #Workflow #Router #Regression test
#623 · provider/a2aprovider · Merged Jul 23, 2026 · +76 −1

setTaskID appended the task ID to session state unconditionally, but it runs once per streamed event and every event for a task carries the same ID — so one ID was appended dozens of times per turn, and follow-up requests carried duplicate ReferenceTasks that compounded across turns. Guarding the append with slices.Contains records each linked task once while keeping distinct multi-task IDs.

#Go #Providers #A2A #Regression test
#621 · agent/harness/agentmode · Merged Jul 23, 2026 · +20 −3

The Go agentmode defaultInstructions carried a second bullet absent from the .NET AgentModeProvider template, and it baked the built-in plan/execute mode names into the prose — misleading when custom Modes are configured via Config.Modes. Removing it restores verbatim parity with .NET and drops the hardcoded mode assumption.

#Go #Docs #Regression test
#598 · internal/concurrent · Merged Jul 23, 2026 · +62 −0

Queue.Dequeue advanced the slice with q.items = q.items[1:] but never cleared the old head, so the backing array kept a live pointer to the dequeued value until it reallocated — retaining whole object graphs for reference element types like Queue[*execution.MessageEnvelope]. This zeroes the head slot and drops the backing array when empty, mirroring slices.Delete semantics.

#Go #Concurrency #Workflow #Regression test
#522 · workflow/checkpoint · Merged Jul 20, 2026 · +44 −3

inMemoryManager holds an unsynchronized Store map but is keyed by session ID with no documented single-use restriction, so sharing one manager across concurrent runs is natural — and concurrent Commit calls raced the map and could crash with fatal: concurrent map writes. This guards Store with a sync.Mutex across Commit, Lookup, and RetrieveIndex; the critical sections are pure in-memory ops.

#Go #Checkpoint #Concurrency #Data race
#507 · workflow · Merged Jul 20, 2026 · +52 −1

Builder.AddDirectEdge appended every connection — including conditional edges — to wb.conditionlessConnections, but the dedup check treated any entry there as a pre-existing conditionless edge. So a conditional edge added first on a source-to-target pair poisoned the set: a later legitimate conditionless edge was wrongly rejected, or silently dropped via AddChain. Guarding the append with condition == nil fixes it.

#Go #Workflow #Router #Regression test
#567 · provider/a2aprovider · Merged Jul 20, 2026 · +16 −0

The OpenAI, Gemini, and Copilot providers document their NewAgent constructors, but the A2A, AG-UI, and Anthropic providers did not. This adds godoc to those undocumented exported symbols — including a2aprovider.AgentConfig/TaskID and the AG-UI config types and JSON handler — matching the existing Gemini style. Docs-only, no behavior change.

#Go #Providers #Docs
#553 · provider/copilotprovider · Merged Jul 20, 2026 · +26 −0

assistantUsageUpdate mapped input, output, total, and cache-read tokens from a Copilot assistant.usage event but never read ReasoningTokens, so the framework's UsageDetails.ReasoningTokenCount stayed 0 for reasoning models and callers got no reasoning breakdown. This populates ReasoningTokenCount; since reasoning tokens are a subset of output, TotalTokenCount is unchanged, matching the OpenAI and Gemini providers.

#Go #Providers #Copilot #Usage
#506 · provider/aguiprovider · Merged Jul 19, 2026 · +73 −1

The aguiprovider streaming run passed the caller's context straight to the SSE client's Stream, so on any early return — consumer stopping mid-stream or a decode error — the request was never cancelled and the client's background reader goroutine (owning the response body) leaked until ReadTimeout, or indefinitely when it is 0. This derives a cancellable context with defer cancel(), matching the a2a and copilot providers.

#Go #Providers #AG-UI #Streaming
#529 · provider/anthropicprovider · Merged Jul 19, 2026 · +52 −0

buildMessageParams adopted the caller's MessageNewParams by struct copy, then appended to System and Messages — but a struct copy shares the slice backing arrays, so appends into spare capacity silently corrupted caller data and reused params accumulated duplicated system blocks. This clones the two mutated slice fields after adopting the option, aligning with the gemini provider.

#Go #Providers #Anthropic #Regression test
#503 · agent/harness/todo · Merged Jul 19, 2026 · +78 −10

The todo provider keyed its per-session lock registry by Session.ServiceID(), which breaks the lock's guarantee: an empty ID collapses unrelated sessions onto one lock, distinct sessions can share an ID, the ID is mutable, and entries never got removed. This re-keys by session identity via weak.Pointer with a runtime.AddCleanup to drop entries after GC, restoring parity with .NET's identity-keyed ConditionalWeakTable (following #491).

#Go #Concurrency #Data race #Regression test
#541 · tool/mcptool · Merged Jul 18, 2026 · +77 −0

agentContentToMCPContent gave image and audio media types dedicated content types, but every other type — including text/* — fell through to the default and was written to Resource.Blob as base64 binary. Since the reverse mapping reconstructs a TextContent from Resource.Text, a text DataContent round-tripped through MCP became an unreadable blob. This adds a text case populating Resource.Text; binary payloads still use Blob.

#Go #MCP #Regression test
#544 · provider/anthropicprovider · Merged Jul 18, 2026 · +81 −2

In buildMessageParams, a FunctionCallContent with empty Arguments left args as a nil map, which serializes to "input": null — but Anthropic requires a tool_use block's input to be an object and rejects null with a 400. So replaying history containing a no-argument tool call broke the request. The fix defaults args to an empty object when nil, so input is always {}.

#Go #Providers #Anthropic #Nil-safety
#491 · agent/harness/agentmode · Merged Jul 17, 2026 · +135 −13

The agentmode provider's mode_set/mode_get tools read and wrote the session-state map with no synchronization and shared a single *state pointer across tool closures. Since toolautocall runs multiple calls from one response on separate goroutines when AllowConcurrentInvocations is set, concurrent calls raced both the map and the shared pointer. This adds per-session locking (mirroring the todo provider) and reloads state inside the lock.

#Go #Concurrency #Data race #Regression test
#509 · provider/geminiprovider · Merged Jul 17, 2026 · +58 −3

The geminiprovider streaming path appended a UsageContent for every chunk with non-nil UsageMetadata, but Gemini reports usage cumulatively — so Response.Usage(), which sums each UsageContent, over-counted tokens (running totals 12 then 15 reported 27 instead of 15). The fix tracks the latest metadata and emits a single UsageContent at stream end, using the authoritative final total.

#Go #Providers #Gemini #Usage
#508 · tool/mcptool · Merged Jul 17, 2026 · +73 −21

agentContentToMCPContent dereferenced the concrete content pointer without a nil check, so a tool returning a typed-nil message.Content (e.g. var ec *message.ErrorContent; return ec, nil) satisfied the interface, skipped the case nil arm, and panicked on the field dereference. The fix returns from each pointer case only when the value is non-nil, letting typed-nil fall through to the JSON fallback where it marshals to "null".

#Go #MCP #Nil-safety #Panic guard
#504 · workflow/inproc · Merged Jul 17, 2026 · +78 −1

The inproc runner wrote its checkpoints and lastCheckpointInfo fields from the background run-loop goroutine during supersteps while consumers read them through the public Checkpoints()/LastCheckpoint() accessors, with no synchronization — so slices.Clone raced the append. This adds a checkpointMu guarding all four access sites, deliberately not held across the blocking Commit I/O call.

#Go #Workflow #Checkpoint #Data race
#511 · workflow/internal/execution · Merged Jul 17, 2026 · +66 −2

selectedTargetIDs indexed edge.Connection.SinkIDs[id] directly with each index from a caller-supplied fan-out Assigner (attached via the public WithEdgeAssigner), so an assigner yielding an out-of-range or negative index panicked the workflow runtime during dispatch. The fix skips any index outside [0, len(SinkIDs)), so a misbehaving user assigner is contained rather than crashing the run.

#Go #Workflow #Panic guard #Regression test
#512 · agent · Merged Jul 17, 2026 · +28 −1

AllOptions panicked with "option type mismatch" when a matched option's Value() could not assert to T, whereas the sibling GetOption returned gracefully — a WithTool(nil) option (e.g. from a []tool.Tool with a nil element) triggered it, aborting tool collection in toolautocall and every provider. The fix continues past absent values, mirroring GetOption, so a nil tool is skipped.

#Go #Nil-safety #Panic guard #Regression test
#497 · agent/skills/fsskills · Merged Jul 17, 2026 · +31 −0

SearchDepth controls only resource and script discovery within an already-discovered skill directory; skill-directory discovery (locating SKILL.md directories) is bounded independently, matching .NET — but that scope was not explicit, which led to a misread. This documents the intended scope on SourceOptions.SearchDepth and searchForSkills, and adds a test locking in that a large SearchDepth does not widen directory discovery. No behavior change.

#Go #Skills #Docs #Regression test
#473 · provider/openaiprovider · Merged Jul 17, 2026 · +96 −21

The non-streaming Chat Completions path indexed resp.Choices[0] unconditionally, but some OpenAI-compatible services return HTTP 200 with an empty choices array — notably Azure OpenAI when a prompt is blocked by a content filter — panicking with index out of range [0] with length 0. The streaming path already guarded this; the fix returns an error through the response stream instead of panicking.

#Go #Providers #OpenAI #Panic guard
#489 · workflow · Merged Jul 14, 2026 · +16 −1

RouteBuilder.AddHandlerRaw guards against registering a typed handler for PortableValue, but the check was dead code: messageType is already a reflect.Type, so reflect.TypeOf(messageType) returned the interface's dynamic type and never matched. The guard was always false, silently accepting a PortableValue handler that collided with the router's dedicated portable-value path. The fix compares the reflect.Type directly.

#Go #Workflow #Reflection #Router
#472 · agent/harness/toolapproval · Merged Jul 14, 2026 · +92 −22

The tool approval middleware passed every update straight into splitApprovalRequestContents, which iterates u.Contents, with no nil check. Nil updates are legal in a response stream — the run loop skips them and toolautocall forwards them — so composing toolapproval with toolautocall or any provider yielding a nil update panicked with a SIGSEGV. The fix forwards nil updates downstream, preserving stream semantics.

#Go #Middleware #Nil-safety #Panic guard

Open

100 proposed fixes under active review, spanning the model-provider layer (OpenAI, Anthropic, Gemini, Copilot, A2A, AG-UI, Foundry), the workflow engine, and the agent runtime. A representative selection — the full set is on GitHub:

Browse all 100 open PRs on GitHub →

Closed

Filed to make an issue concrete; superseded, batched differently, or deferred to maintainer preference. Included for a complete record.

#587 · examples · Closed Jul 22, 2026 · +1290 −503

Ported a batch of upstream .NET examples that had no Go equivalent — agent HTTP hosting, OpenTelemetry instrumentation, the handoff and magentic workflow-orchestration patterns, and workflow visualization — plus fixes to existing example bindings. Closed in favor of landing the examples individually alongside their subsystems rather than as one large batch.

#Go #Examples #Workflow #Observability
#470 · provider/anthropicprovider · Closed Jul 2026 · +99 −6

Two streaming bugs broke parallel tool use: every content_block_stop re-emitted all accumulated function calls (executing the first tool call repeatedly), and argument JSON was seeded with the {} input placeholder so deltas produced {}{"city":"Paris"}. Emits each call once on its block stop and accumulates input_json_delta fragments from scratch.

#Go#Providers#Anthropic#Streaming
#481 · internal/azaiprojects · Closed Jul 2026 · +190 −14

The Azure AI Projects toolbox request builders interpolated a caller-supplied name directly into the request path. A name that isn't a single intact segment (./.. traversal, or a raw / percent-encoded separator) could alter the request target. Ports the upstream .NET EnsureSafeToolboxName validation to Go, validating the raw and percent-decoded name.

#Go#Security#Path traversal#Azure

Explore more

See the full open-source record, or the multi-agent systems built on this framework.

All my PRs on GitHub → Open Source → Genie →