Build a Concurrent Multi-Agent Repository Digest with Go
On July 10, 2026, Microsoft released the public preview of the Microsoft Agent Framework for Go, bringing the framework that already ships 1.0 SDKs for .NET and Python to a third language.

Introduction
On July 10, 2026, Microsoft released the public preview of the Microsoft Agent Framework for Go, bringing the framework that already ships 1.0 SDKs for .NET and Python to a third language. This guide builds a weekly engineering digest generator: three specialist agents analyze your repository's commits, issues, and dependency changes concurrently, and a fourth agent synthesizes their findings into one report. The fan-out runs on goroutines, the cancellation runs on context.Context, and the whole thing compiles to a single binary you can drop into cron.
What the Microsoft Agent Framework is
The Microsoft Agent Framework is the merger of AutoGen and Semantic Kernel into one supported product. It hit 1.0 general availability for .NET and Python in April 2026 with a long-term-support commitment, and at Build 2026 Microsoft layered on the Agent Harness, hosted agents, and CodeAct. The Go SDK announced July 10 brings that framework to a language built for infrastructure: agents, tools, MCP support, middleware, approvals, and multi-agent workflows. The import path is github.com/microsoft/agent-framework-go; providers cover Microsoft Foundry, Azure OpenAI, OpenAI-compatible endpoints, Anthropic, and Gemini, plus A2A for agent-to-agent calls.
Why Go is a good fit for agent fan-out
Concurrency in Python agent code means an async runtime, an event loop, and a library ecosystem that is still partly synchronous underneath. Concurrency in Go is the language: a goroutine per agent, an errgroup to collect failures, one context.Context that cancels every in-flight LLM call when the deadline passes. Deployment seals the case: go build emits one static binary, no virtualenv, no interpreter version, no install-time dependency resolution
Create your first agent
Start a module and pull the preview SDK plus the Azure identity package:
mkdir repo-digest && cd repo-digest
go mod init repodigest
go get github.com/microsoft/agent-framework-go
go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
go get golang.org/x/sync/errgroup
export FOUNDRY_ENDPOINT="https://your-project.services.ai.azure.com"
export FOUNDRY_MODEL="gpt-5.1-mini"The Foundry provider is the documented path in the preview announcement, authenticating with DefaultAzureCredential (the SDK also ships OpenAI-compatible and Anthropic providers if your stack lives elsewhere):
package main
import (
"context"
"fmt"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/microsoft/agent-framework-go/foundryprovider"
)
func newAgent(instructions string) (*foundryprovider.Agent, error) {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, fmt.Errorf("credential: %w", err)
}
return foundryprovider.NewAgent(
os.Getenv("FOUNDRY_ENDPOINT"),
cred,
foundryprovider.ModelDeployment(os.Getenv("FOUNDRY_MODEL")),
foundryprovider.AgentConfig{Instructions: instructions},
)
}
func main() {
agent, err := newAgent("You summarize git history for engineers. Be specific and terse.")
if err != nil {
panic(err)
}
out, err := agent.RunText(context.Background(), "Say ready.").Collect()
if err != nil {
panic(err)
}
fmt.Println(out)
}RunText returns a streaming handle; .Collect() drains it into a final string. That two-call shape is the core of the preview API, and everything below composes it.
Feed the specialists real repository data
Agents reasoning over stale summaries produce stale digests, so each specialist gets raw material straight from the repo. Plain os/exec does the collection:
import (
"os/exec"
"strings"
)
func gitLog(repoPath string, days int) (string, error) {
cmd := exec.Command("git", "-C", repoPath, "log",
fmt.Sprintf("--since=%d.days", days),
"--pretty=format:%h %an %s", "--stat")
out, err := cmd.Output()
return string(out), err
}
func diffSummary(repoPath string, days int) (string, error) {
cmd := exec.Command("git", "-C", repoPath, "log",
fmt.Sprintf("--since=%d.days", days),
"-p", "--", "go.mod", "go.sum", "package.json", "requirements.txt")
out, err := cmd.Output()
if len(out) > 20000 {
out = out[:20000] // cap the prompt, dependency churn can be enormous
}
return string(out), err
}The 20,000-byte cap is not decoration. An unbounded -p diff of a lockfile update will happily hand your model half a megabyte of hashes and bill you for the privilege.
Fan out three specialists on goroutines
Each specialist is an agent with narrow instructions and one slice of the data. The fan-out is an errgroup with a shared timeout:
import (
"time"
"golang.org/x/sync/errgroup"
)
type section struct {
title string
body string
}
func runSpecialists(ctx context.Context, repoPath string) ([]section, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
commits, err := gitLog(repoPath, 7)
if err != nil {
return nil, err
}
deps, err := diffSummary(repoPath, 7)
if err != nil {
return nil, err
}
specs := []struct {
title, instructions, input string
}{
{"Commit Activity",
"You analyze git commit logs. Report themes, risky areas, and who worked on what. Max 150 words.",
commits},
{"Dependency Changes",
"You review dependency-file diffs. Flag major version bumps, new packages, and removals. Say 'none' if qui
deps},
{"Release Risk",
"You assess a week of commits for release risk: migrations, config changes, deleted tests. Max 100 words."
commits},
}
results := make([]section, len(specs))
g, gctx := errgroup.WithContext(ctx)
for i, s := range specs {
g.Go(func() error {
agent, err := newAgent(s.instructions)
if err != nil {
return err
}
out, err := agent.RunText(gctx, s.input).Collect()
if err != nil {
return fmt.Errorf("%s: %w", s.title, err)
}
results[i] = section{title: s.title, body: out}
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}Three LLM calls run in parallel, and the wall-clock cost of the digest is the slowest agent, not the sum. If any agent fails, errgroup cancels gctx, which cancels its siblings' in-flight requests mid-token. Try writing that behavior in a hand-rolled Python loop and you will discover why checkpointed workflow engines exist.
Synthesize the digest
The synthesizer is just another agent whose input is the specialists' output:
func synthesize(ctx context.Context, sections []section) (string, error) {
agent, err := newAgent(
"You are an engineering lead writing a weekly digest. Merge the sections into one report: " +
"a 3-sentence summary, then each section tightened. Plain language, no filler.")
if err != nil {
return "", err
}
var b strings.Builder
for _, s := range sections {
fmt.Fprintf(&b, "## %s\n%s\n\n", s.title, s.body)
}
return agent.RunText(ctx, b.String()).Collect()
}This two-layer shape (parallel specialists, serial synthesizer) covers a surprising share of real multi-agent work: code review, due diligence, log triage, competitive analysis. The framework's workflow engine adds routing, checkpoints, and human-review gates on top, but the preview's documentation is thinnest exactly there, so this build composes with the language instead, which for pipelines this size is the better tool anyway.
Ship it as one binary
func main() {
repoPath := "."
if len(os.Args) > 1 {
repoPath = os.Args[1]
}
ctx := context.Background()
sections, err := runSpecialists(ctx, repoPath)
if err != nil {
fmt.Fprintln(os.Stderr, "specialists:", err)
os.Exit(1)
}
digest, err := synthesize(ctx, sections)
if err != nil {
fmt.Fprintln(os.Stderr, "synthesize:", err)
os.Exit(1)
}
fmt.Println(digest)
}go build -o repo-digest .
./repo-digest /path/to/your/repo > digest.md
# cron, every Friday at 16:00
# 0 16 * * 5 /usr/local/bin/repo-digest /srv/product-repo | mail -s "Weekly digest" team@example.comOne artifact, no runtime dependencies beyond git itself.
Go goroutines vs Python asyncio for agent fan-out
| | Go + Agent Framework for Go | Python + Agent Framework 1.0 | |---|---|---| | Concurrency unit | Goroutine, native | Coroutine on an event loop | | Fan-out | errgroup.Go | asyncio.gather | | Cancellation | One context cancels all in-flight calls | Cooperative, task cancellation is best-effort | | Blocking calls | Fine, scheduler handles them | Block the loop unless offloaded | | Deployment | Single static binary | Interpreter + venv + lockfile | | Framework maturity | Public preview, July 10, 2026 | 1.0 GA, LTS commitment | | Ecosystem | Thin but growing | Full: harness, hosted agents, CodeAct |
Limitations
This is a public preview and Microsoft says the APIs may evolve, so pin your commit and read the changelog before upgrading (preview SDKs have broken politer people than us). The workflow engine's routing, checkpoints, and human-review gates exist in the framework but are exactly where preview documentation is thinnest, which is why this tutorial composes with goroutines instead. The Foundry provider path assumes an Azure identity; teams outside Azure should use the OpenAI-compatible provider. There is no retry logic in this build, a fan-out multiplies token spend by the number of specialists whether or not the week was interesting, and a digest agent cannot tell you about the commit someone never pushed.
The full working example
// main.go: concurrent multi-agent repo digest
// build: go build -o repo-digest .
package main
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/microsoft/agent-framework-go/foundryprovider"
"golang.org/x/sync/errgroup"
)
type section struct {
title string
body string
}
func newAgent(instructions string) (*foundryprovider.Agent, error) {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, fmt.Errorf("credential: %w", err)
}
return foundryprovider.NewAgent(
os.Getenv("FOUNDRY_ENDPOINT"),
cred,
foundryprovider.ModelDeployment(os.Getenv("FOUNDRY_MODEL")),
foundryprovider.AgentConfig{Instructions: instructions},
)
}
func gitLog(repoPath string, days int) (string, error) {
cmd := exec.Command("git", "-C", repoPath, "log",
fmt.Sprintf("--since=%d.days", days),
"--pretty=format:%h %an %s", "--stat")
out, err := cmd.Output()
return string(out), err
}
func diffSummary(repoPath string, days int) (string, error) {
cmd := exec.Command("git", "-C", repoPath, "log",
fmt.Sprintf("--since=%d.days", days),
"-p", "--", "go.mod", "go.sum", "package.json", "requirements.txt")
out, err := cmd.Output()
if len(out) > 20000 {
out = out[:20000]
}
return string(out), err
}
func runSpecialists(ctx context.Context, repoPath string) ([]section, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
commits, err := gitLog(repoPath, 7)
if err != nil {
return nil, err
}
deps, err := diffSummary(repoPath, 7)
if err != nil {
return nil, err
}
specs := []struct {
title, instructions, input string
}{
{"Commit Activity",
"You analyze git commit logs. Report themes, risky areas, and who worked on what. Max 150 words.",
commits},
{"Dependency Changes",
"You review dependency-file diffs. Flag major version bumps, new packages, and removals. Say 'none' if qui
deps},
{"Release Risk",
"You assess a week of commits for release risk: migrations, config changes, deleted tests. Max 100 words
commits},
}
results := make([]section, len(specs))
g, gctx := errgroup.WithContext(ctx)
for i, s := range specs {
g.Go(func() error {
agent, err := newAgent(s.instructions)
if err != nil {
return err
}
out, err := agent.RunText(gctx, s.input).Collect()
if err != nil {
return fmt.Errorf("%s: %w", s.title, err)
}
results[i] = section{title: s.title, body: out}
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
func synthesize(ctx context.Context, sections []section) (string, error) {
agent, err := newAgent(
"You are an engineering lead writing a weekly digest. Merge the sections into one report: " +
"a 3-sentence summary, then each section tightened. Plain language, no filler.")
if err != nil {
return "", err
}
var b strings.Builder
for _, s := range sections {
fmt.Fprintf(&b, "## %s\n%s\n\n", s.title, s.body)
}
return agent.RunText(ctx, b.String()).Collect()
}
func main() {
repoPath := "."
if len(os.Args) > 1 {
repoPath = os.Args[1]
}
ctx := context.Background()
sections, err := runSpecialists(ctx, repoPath)
if err != nil {
fmt.Fprintln(os.Stderr, "specialists:", err)
os.Exit(1)
}
digest, err := synthesize(ctx, sections)
if err != nil {
fmt.Fprintln(os.Stderr, "synthesize:", err)
os.Exit(1)
}
fmt.Println(digest)
}When to use this
Reach for the Agent Framework for Go when your agents live inside infrastructure that is already Go, when fan-out concurrency is the shape of the problem, or when single-binary deployment matters more than ecosystem breadth. Stay on the Python or .NET SDKs when you need the mature end of the framework today: the harness, hosted agents, checkpointed workflows. The preview will move fast over the next few months, and the language that owns cloud infrastructure tooling now has a first-party agent framework. That combination is worth an afternoon of your time.
You might also like
Keep reading from the journal.
July 6, 2026AI
Map every system once, not to each other
A canonical model turns integration forty-two into a mapping
June 23, 2026AI
Nobody asked for the AI feature
Building for the room you pitch in, not the desk it lands on
June 2, 2026Agents
The agent that delegated
On May 28, 2026, Anthropic shipped the Agent SDK alongside Claude Opus 4.8. The SDK lets a single orchestrator spawn parallel subagents, each with its own tools and structured output schema. The agents run concurrently, return typed results, and the orchestrator synthesizes them.