Linear Skipped the Agent Frameworks and Wrote Their Own Harness
Shallow tools, just-in-time skills, and a defensible case for a custom harness
⚡TLDR
Linear Agent, the assistant that lives inside Linear, does most of its valuable work in ways nobody scripted, and that premise cuts against engineering instinct. The engineering effort went into boundaries, and those boundaries live in five places:
A short system prompt
Tool schemas that make invalid actions impractical
An explicit model of the product
Per-run scoping through system skills
A custom harness underneath.
🧱 The spec is the walls
Traditional software quality is a shrinking act: reduce the space of possible outcomes until the same action produces the same result every time. Linear Agent’s value comes from outcomes the team never enumerated, so shrinking the space would shrink the product. The engineering moved into the boundaries instead to fix what the agent can never do, and leave the middle open for it to improvise.
📜 A prompt that stays out of the way
The system prompt covers fundamentals and little else: a communication style that adapts to the surface (more conversational in Slack than in a Loop, Linear’s async discussion doc), hard limits on sensitive topics, a rule that scope-expanding actions require confirmation, the taxonomy of Linear itself, and default opinions for interpreting terse prompts. User prompts are often a sentence long, so the agent needs a strong sense of when to infer intent and when to ask a clarifying question.
The looseness is deliberate. In a Slack thread the agent might read the room and land a well-timed joke, and the sensitive-topic boundary still holds while it does.
🔧 Constraints belong in the tools
Constraints work better encoded in tool design than spelled out in prose. Shape the parameters so invalid actions are impractical to take, the way a good abstraction or a good interface makes correct usage the obvious usage.
A prompt instruction is advisory. The model weighs it against everything else in context, and under pressure it loses. A tool schema gets enforced before anything executes. The side benefit is longevity: because the action space stays loosely scripted and the tools compose, every model upgrade makes the same toolset more capable with zero migration work.
🧩 The opposite shape of a coding agent
Coding agents run on a handful of deep primitives like read_file and run_command, and code plus shell are so saturated in training data that the agent has decent instincts even in unfamiliar territory. Linear Agent is the inverse: dozens of shallow tools, each tied to one product operation, over concepts that barely register in training data. Next to code, Linear barely shows up in the training data at all.
So every product area the agent supports needs tools for reading and changing the data, an explanation of what that data means and how it connects to the rest of the product, and working principles for using the feature well. Customer Requests, for example: the skill hands the agent request tools plus the semantics (each request captures one customer’s feedback and usually hangs off an issue or project). It also carries opinionated guidance, like aggregating patterns across requests when drafting a project spec.
📦 Skills, loaded just in time
The mechanism for all this context is the system skill: a fragment of the system prompt bundled with the tools it describes. Most requests touch a thin slice of the product, so before each run the agent predicts which skills it will need from the prompt and the surface it was invoked from. Summon it from a project’s page in Linear, or from that project’s Slack channel, and the projects skill comes preloaded.
Mid-run, it loads more on its own. Ask for a project update draft and it might start with the projects and project-updates skills, realize halfway through that it wants recent issues and PRs, and pull those skills in itself. Each thread stays focused while the agent’s total surface keeps growing, the same progressive-disclosure logic we saw in How LinkedIn Built Its Agentic Stack.
🔒 No SDK, no CLI, no GraphQL
The obvious alternative to all this tooling was handing the agent the Linear SDK with a coding environment, a CLI, or raw GraphQL. It would have made the agent more capable. It also multiplies the ways a run can go wrong, because the underlying data model supports far more actions than any single request needs, and some UI concepts require interpretation before they map onto it. The failure mode shows up on long-tail requests: with no obvious path to completion, the agent starts guessing, and each action gets a little more speculative than the last.
The cost of keeping low-level access out is that the agent occasionally refuses something it might have brute-forced. That seems like the right price for an agent operating directly on customer workspaces. A coding agent can afford an escape hatch because its output is a diff a human reviews before merge. This one’s actions take effect the moment they execute.
🏗️ The custom harness
Underneath, the agent runs on a custom stack from the model-provider APIs up. A provider-agnostic client gives one wire and storage format for threads, the agent loop runs on a durable workflow engine, and a streaming layer parses rich elements like mentions and widgets before they reach the product.
Harness libraries assume you provide a prompt and a set of tools upfront, call run, and wait for a final response. That works for straightforward interactions. This agent needs control while a run is still in progress.
Skill loading is the clearest case. When the agent loads a skill mid-run, the new tools have to be callable immediately, and the naive implementation invalidates the provider’s prefix cache, so every later turn reprocesses the full context at full price. Their client injects the tools the moment the skill’s tool call resolves, with the cache prefix intact.
Approval logic is another. Most libraries decide whether a tool call needs confirmation from the tool name and parameters alone. The custom loop makes that call with conversational context: the agent deletes things it created earlier in the same thread without asking, and pauses mid-run before touching an issue that predates the conversation. It posts freely to most comment threads, and asks first when a thread syncs to a public repository.
Sub-agents force the same kind of mid-run control. To the parent, a sub-agent is a synchronous tool call. Underneath, the parent’s turn suspends mid-call and gives up its server resources while the child works. When the child finishes, the workflow engine resumes the thread.
That list is how build-versus-buy should be argued: the specific behaviors the libraries block, each with a cost attached. A team without a list like that is better served by the library.
🏁 The line will move
Every one of these decisions is a bet on where the line between possibility and predictability sits, and that line will keep shifting. The constraints encoded in tools and data access hold up regardless of how good models get. The withheld GraphQL API is the one I’d watch: as models get better at recovering from dead ends, that trade will get revisited, and some version of low-level access will probably return behind heavier approval gates, much like the guarded remote setups we covered in Building Secure, Scalable Remote MCP Servers.
If you’re building an agent into a product, separate your constraints into two lists: the ones that are load-bearing safety, and the ones compensating for what today’s models still get wrong. The second list shrinks every quarter.
Which of your agent’s guardrails would you delete if the model got twice as smart tomorrow? Hit reply, we read everything.
Source: “How we built Linear Agent” by Matthijs Wolting, Linear blog, August 10, 2026.

