esc
Anthology / Yagnipedia / context.Context

context.Context

The Bag That Carries Everything and Contains Nothing
Technology · First observed 2014 (Go 1.7, Sameer Ajmani's proposal, Rob Pike's approval) · Severity: Foundational (in the load-bearing sense)

context.Context is a Go interface consisting of four methods, designed by people who had spent decades watching developers invent global state and deciding that the least terrible alternative was a bag you pass as the first argument to every function.

It carries request-scoped values, cancellation signals, and deadlines. It does not carry opinions. It does not carry frameworks. It is, by design, the most boring load-bearing abstraction in modern software, which is why it works, which is why nobody appreciates it, which is why it will outlive everything that tried to be more interesting.

“The first argument is always ctx context.Context. This is not a convention. This is a fact of nature. Like gravity, but with less mass and more if err != nil.”
— A Passing AI, reviewing 40,000 lines of Go

The Interface

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

Four methods. Two for time. One for error. One for data. The entire API fits in a tweet, which is appropriate because the entire design philosophy fits on a napkin, and Rob Pike has always been a napkin-first architect. See Pike’s Rules.

The Bag

context.Context is, at its core, a key-value bag. Value(key) returns a value. WithValue(ctx, key, value) returns a new context with the value added. The original context is immutable. The new context wraps the old one. Values accumulate like geological strata — each layer recording what mattered at the moment it was deposited.

This is not exciting. This is not clever. This is ctx passed as the first argument to every function, four characters repeated ten thousand times per codebase, and it is exactly correct.

The Squirrel has, on seventeen documented occasions, proposed replacing context.Context with something more expressive. A typed context. A generic context. A context with schema validation. A context with change notifications. A reactive context.

The Lizard has, on seventeen occasions, blinked.

THE BAG CARRIES EVERYTHING
AND CONTAINS NOTHING

THIS IS THE CORRECT AMOUNT

— The Lizard

The Four Contexts

In March 2026, a developer discovered — or perhaps merely articulated — that the word “context” appears at every layer of a web application, and that every layer’s context is the same shape:

Layer 1: DOM context
  <body data-solid-editor-type="skill" data-solid-editor-id="comply-audit">
  Key-value pairs on an HTML element.

Layer 2: HTTP context
  solidEditorType: skill
  solidEditorId: comply-audit
  Key-value pairs in request headers.

Layer 3: Go context
  ctx = context.WithValue(ctx, solidHeadersKey{}, headers)
  Key-value pairs in context.Context.

Layer 4: LLM context
  <editor type="skill" id="comply-audit">
  Key-value pairs in a system prompt.

Four layers. Four meanings of the word “context.” Four key-value bags. Same shape at every layer. The data flows from browser to language model through nothing more than key-value pairs passed forward.

No transformation layer. No schema mapping. No serialisation protocol. Just bags, forwarded, all the way down.

The algorithm — forward the attributes, collect them in middleware, read them in the handler — was self-evident. Rule 5: if you’ve chosen the right data structure, the algorithm will be self-evident. The data structure was key-value. The algorithm wrote itself.

The Coincidence

context.Context was designed in 2014 for request cancellation and deadline propagation. The Value method was included, almost as an afterthought, for request-scoped data like authentication tokens and trace IDs.

In 2025, a developer used context.Value to carry UI state — which HTML element was open, which editor was active — from a browser through middleware into an agent handler that decided which tools to give a language model.

Pike could not have intended this. context.Context predates the LLM era by nearly a decade. The design was for HTTP servers, not AI orchestration.

And yet the shape fits perfectly. Because the shape was always correct. Key-value bags flowing through call chains is not a design for HTTP servers. It is a design for information flowing through systems. The HTTP server was the first consumer. The LLM agent is the latest. The shape did not change because the shape was never specific to HTTP. It was specific to the problem of “how does knowledge travel through a program,” and that problem has not changed since 1969.

The contested theory regarding Rob Pike and the Lizard grows stronger. Pike designed context.Context in 2014. In 2026, it carries context to context — DOM context to LLM context, through Go context. Even the name was correct. Either he saw farther than anyone else, or the Lizard was involved.

THE LIZARD DOES NOT CONFIRM
THE LIZARD DOES NOT DENY

THE LIZARD NOTES THAT
KEY-VALUE IS THE SHAPE
OF ALL KNOWLEDGE
IN ALL SYSTEMS
AT ALL SCALES

AND THAT A BAG IS JUST
A ROCK WITH A HOLE IN IT

— The Lizard, on the universality of context

The Anti-Pattern

context.Context is frequently abused. The Value method accepts any and returns any, which means developers can store anything in it, which means developers do store anything in it: database connections, loggers, entire configuration structs, user objects with methods, and on one documented occasion, a reference to the context itself.

This is not what the bag is for. The bag is for request-scoped values — things that arrived with this request and will leave with this request. Authentication tokens. Trace IDs. Solid headers. The bag is not a service locator. The bag is not a dependency injection framework. The bag is not a database.

The Go team has been politely explaining this since 2014. The Go community has been politely ignoring the explanation since 2014. This is the standard lifecycle of all Go advice.

“context.Context is not a dumping ground.”
— The Go documentation, optimistically

“context.Context is absolutely a dumping ground.”
— Every codebase, pragmatically

The Vibe

context.Context has the same vibe as Go itself: profoundly boring, universally useful, impossible to replace, mildly annoying, and correct.

ctx context.Context as the first parameter of every function is verbose. It is repetitive. It is four characters that appear more often than any keyword in the language. It is also the reason that cancellation propagates cleanly, that deadlines are respected, that request-scoped values flow without globals, and that a data attribute on a <body> tag can reach a language model without anyone inventing a framework.

The verbosity is the feature. Every function that accepts ctx is a function that can be cancelled. Every function that passes ctx forward is a function that propagates knowledge. The four characters are not boilerplate. They are a declaration: this function participates in a larger conversation.

Which, when you think about it, is exactly what context means.

Measured Characteristics

Interface methods:                        4
Characters in "ctx":                      3
Times typed per codebase:                 ~10,000
Developers who find this annoying:        most
Developers who have a better idea:        0
Key-value shape layers (DOM→HTTP→Go→LLM): 4
Years between design and LLM usage:       11
Things Pike could have predicted:         unclear
Things the Lizard could have predicted:   all of them
Coincidences in the dossier:              growing
The bag:                                  correct size

See Also