Linear Ticket Formatting Guide — Agentic Use
This guide defines how to structure Linear issues, milestones, and projects so that any agent (human or AI) can pick up a ticket and execute it without needing to ask clarifying questions. The goal is self-contained, implementation-ready tickets.
These patterns were extracted from the ERA-1085–ERA-1108 incident resolution project, which successfully resolved a P0 LiveKit connectivity outage across 24 issues, 3 milestones, and 1 project. They are reinforced by research from Anthropic's context engineering guidelines, GitHub's analysis of 2500+ agent instruction files, Addy Osmani's spec-driven development framework, and SWE-bench findings on structured task descriptions.
Core Principles
1. Every ticket is a specification, not a reminder. If an agent reads the ticket and still needs to search the codebase to understand what to do, the ticket is incomplete.
2. When an agent underperforms, treat it as a spec deficiency. The question is always "what context was missing?" — not "why is the agent bad?" Iterating on specifications improves outcomes faster than any other approach.
3. Instruction volume degrades performance. LLM performance degrades as instruction quantity increases (Osmani, 2025). Keep tickets focused on a single change. Break multi-change work into separate issues with blocking dependencies.
Issue Structure
Every issue follows this skeleton, with sections adapted based on issue type:
## Context
Why does this issue exist? What broke, what's missing, or what needs to change?
Include the causal chain — not just "X is broken" but "X broke because Y
happened on [date], which caused Z behavior."
## What to change
Exact files, exact line numbers, exact code blocks.
Show both "before" and "after" states.
## Blast radius
What other files, services, or overlays reference the things being changed?
List cross-file impacts the implementer must check.
## Boundaries
- DO: [safe actions that should be taken without asking]
- ASK FIRST: [high-impact decisions requiring human review]
- NEVER: [categorically forbidden actions]
## Why this works
Explain the reasoning so the implementer can verify correctness,
not just follow instructions blindly.
## Rollback
What to do if this change makes things worse. Exact revert steps.
## Verification
Concrete commands or steps to confirm the change worked.
Include both positive checks (new behavior works) and negative checks
(old broken behavior is gone).
Section-by-Section Guide
Context
The Context section answers: why are we doing this?
Required elements:
- Root cause — what introduced the problem, including commit hashes, dates, and PR numbers when available
- Impact — what breaks if this isn't fixed, with evidence (log lines, metrics, user reports)
- Architecture context — how the affected component fits into the broader system
Good example (from ERA-1085):
## Context
The Feb 18 commit (`d921ad7`) added a `turn_servers` block to the LiveKit
server configuration that points to a sidecar STUNner instance at
`127.0.0.1:3478`. This sidecar has **never successfully created a single
TURN allocation** (`allocs:0`, `quota=0`, `license_info=<MISSING>`). When
`turn_servers` is configured, LiveKit's ICE agent incorporates a TURN
gathering phase into candidate generation. Because the sidecar never
responds (not even a 401 challenge), the ICE agent's UDP candidate
gathering is disrupted — iOS and Safari sessions only receive TCP host
candidates from the server and never connect.
This is the **root cause** of all iOS connection failures and intermittent
Safari failures since Feb 18.
Bad example:
## Context
LiveKit connections are broken on iOS. We need to fix the config.
The bad version gives no causal chain, no commit reference, no explanation of the mechanism. An agent reading it would need to investigate from scratch.
What to Change
The most critical section. This must be copy-paste ready.
Required elements:
- File path — exact relative path from repo root
- Line numbers — where to find the code (these may drift, so also include surrounding context)
- Code to remove — shown in a fenced code block with a
# REMOVEcomment - Code to add — shown in a fenced code block representing the desired end state
- Gotchas — edge cases, things that look like they should change but shouldn't, and why
Good example (from ERA-1086):
## What to change
**File**: `era-ingress/k8s/base/statefulset.yaml`
### 1. Remove the `stunner` container (lines 271-306)
Remove the entire container block:
\```yaml
# REMOVE this entire container (lines 271-306):
- name: stunner
image: docker.io/l7mp/stunnerd:1.1.0
...
\```
### 5. Remove `sed` password substitution from LiveKit startup command
Replace the current command:
\```yaml
command:
- /bin/sh
- -c
- |
sed "s/\${STUNNER_PASSWORD}/$STUNNER_PASSWORD/g" ...
\```
With the direct config path (no substitution needed after `turn_servers` removal):
\```yaml
command:
- /livekit-server
args:
- --config
- /etc/livekit/livekit.yaml
\```
Key patterns:
- Number each change (
### 1.,### 2., etc.) for easy tracking - Show the "before" code that's being removed AND the "after" code to replace it
- Explain why the replacement works (e.g., "no substitution needed after
turn_serversremoval") - Call out what to keep when it's adjacent to what's being removed
Decision Points
When a ticket requires a judgment call, frame the decision explicitly rather than leaving it ambiguous.
Good example (from ERA-1088):
### Decision needed
Check the `turn.py` code to understand how `ERA_TURN_PROVIDER` is used:
* If `ERA_TURN_PROVIDER=stunner` only controls client-side ICE server
configuration (pointing clients to the **external** STUNner gateway at
`146.148.64.160`), then **keep it** — the external gateway still works.
* If it also controls anything related to the sidecar, update accordingly.
### Action
1. Read `turn.py` to confirm these env vars are for client-side config only
2. If they only affect client-side config, keep them as-is
3. If there are any references to `127.0.0.1:3478`, remove those code paths
4. Update any comments referencing "sidecar" to say "external gateway"
Blast Radius
This section was missing from the original ERA tickets and caused a real issue during implementation. When ERA-1087 said to delete turn-certificate.yaml, it didn't mention that the staging overlay had a patches/turn-certificate.yaml that patched the now-deleted resource. The implementer had to discover this cross-file dependency independently, which could have caused a broken kustomize build if missed.
Every ticket that deletes or renames a resource must enumerate all references to it.
Good example:
## Blast radius
Files that reference the resources being deleted:
| Deleted resource | Referenced by | Action needed |
|------------------|---------------|---------------|
| `stunner-config.yaml` | `kustomization.yaml` line 36 | Remove from resources list |
| `turn-certificate.yaml` | `kustomization.yaml` line 41 | Remove from resources list |
| `turn-certificate.yaml` | `overlays/staging/patches/turn-certificate.yaml` | Delete patch file |
| `turn-certificate.yaml` | `overlays/staging/kustomization.yaml` line 26 | Remove patch reference |
| `stunner-lb.yaml` | `kustomization.yaml` line 37 | Remove from resources list |
Files that reference the sidecar but should NOT be changed:
- `stunner-livekit-media-route.yaml` — UDPRoute for external gateway, NOT the sidecar
- `configmap.yaml` `ERA_TURN_SERVER_URL` — points to external gateway (146.148.64.160), NOT sidecar
How to build this section: Search the repo for every string that appears in the resource being changed — names, selectors, labels, ports. List every hit. Explicitly mark which ones need changes and which ones are false positives that should be left alone.
Boundaries
Adapted from GitHub's analysis of 2500+ agent instruction files. The three-tier boundary system prevents agents from making high-risk mistakes.
Good example:
## Boundaries
- **DO**: Remove the stunner container, its volumes, and its env vars.
Follow existing patterns for LiveKit command format.
- **ASK FIRST**: Before removing any env var from the ingress container
(some look sidecar-related but control client-side config for the
external gateway).
- **NEVER**: Remove `ERA_TURN_PROVIDER` or `ERA_TURN_SERVER_CREDENTIAL`
from the ingress container — these control client-side ICE servers for
the working external gateway at 146.148.64.160, not the sidecar.
Removing them would break ALL WebRTC connections, not just iOS.
The NEVER boundary is the most impactful. During the P0 implementation, ERA_TURN_PROVIDER=stunner looked like it should be removed (it has "stunner" in the name and the ticket was about removing stunner). But it controls client-side ICE server configuration for the external gateway. Removing it would have broken every platform, not just iOS. An explicit NEVER boundary prevents this class of error.
Reference Patterns
Agents produce better code when pointed to existing patterns in the codebase. Instead of describing the desired code from scratch, reference a working example.
Good example:
## Reference pattern
Follow the same command/args format used by the `cloud-sql-proxy` container
in the same StatefulSet (line 255):
\```yaml
# Existing pattern (cloud-sql-proxy):
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1
args:
- "--structured-logs"
- "--port=5432"
- "era-core-prod:us-central1:era-auth-db"
# Apply same pattern to livekit-server:
- name: livekit-server
image: livekit/livekit-server:v1.9.11
command:
- /livekit-server
args:
- --config
- /etc/livekit/livekit.yaml
\```
This anchors the agent to a proven pattern rather than inventing structure.
Rollback
Every ticket that modifies production infrastructure should include a rollback plan. This is especially important for P0/P1 changes made under time pressure.
Good example:
## Rollback
If iOS connections are still broken after this change:
1. Revert the commit: `git revert <sha>`
2. Push to main — ArgoCD will sync the revert automatically
3. Wait for both pods to restart (watch: `kubectl get pods -n era-core -l app=era-ingress -w`)
4. The old 5/5 container config (with stunner sidecar) will be restored
5. Investigate further — the root cause may not be `turn_servers`
Note: reverting restores the broken sidecar, but connections were
also broken WITH the sidecar. The revert returns to the pre-fix
state, not to a known-good state. If the fix didn't help, the
root cause is elsewhere.
The rollback section should be honest about what "revert" actually restores. In this case, reverting doesn't fix the problem — it restores the broken state. That's important for the implementer to know so they don't waste time reverting when the issue is something else entirely.
Why This Works
This section prevents blind implementation. It gives the implementer a mental model to verify against.
Good example (from ERA-1085):
## Why this works
Before Feb 18, LiveKit had no `turn_servers` config. It advertised host
candidates (`udp4 10.32.x.x:7882` + `tcp4 10.32.x.x:7881`) directly.
The external STUNner gateway (operator-managed, at `146.148.64.160`)
handled client-side TURN relay through the UDPRoute → headless service →
pod IP path. **This architecture worked reliably.**
Removing `turn_servers` restores this exact behavior.
Verification
Every ticket must end with concrete verification steps. These should be runnable commands or observable outcomes, not vague assertions.
Good example (from ERA-1090):
## Verification
\```bash
# Both pods should be 4/4 READY (was 5/5 before sidecar removal)
kubectl get pods -n era-core -l app=era-ingress
# Confirm no stunner container
kubectl describe pod era-ingress-0 -n era-core | grep -A2 "Container ID"
# Confirm LiveKit config has no turn_servers
kubectl exec era-ingress-0 -n era-core -c livekit-server -- cat /etc/livekit/livekit.yaml
\```
| # | Platform | Test | Expected Result |
|---|----------|--------------------|----------------------------------------------|
| 1 | iOS | Join room | Connect in <2s, `connectionType: "turn"` |
| 2 | Chrome | Join room | Connect in <1.5s |
| 3 | Safari | Join room | Connect in <2s |
| 4 | iOS | Hold session 5min | No DTLS timeout, no disconnection |
Bad example:
## Verification
Test that it works on all platforms.
Issue Types and Their Patterns
Bug Fix / Config Change
The most implementation-specific type. Maximize code detail.
Sections: Context → What to change → Why this works → Verification
Characteristics:
- Show the exact broken code and the exact fixed code
- Reference the commit that introduced the bug
- Include log evidence of the failure
- Verification includes "negative checks" (confirm the bad behavior is gone)
Reference: ERA-1085, ERA-1086, ERA-1087
Architecture / Design
For tickets that require a design decision before implementation.
Sections: Context → Target architecture → Design document requirements → Acceptance criteria
Characteristics:
- Include ASCII architecture diagrams
- List all Kubernetes resources or code modules affected
- Call out trade-offs explicitly (e.g., "host networking eliminates IP churn but requires dedicated node pool")
- Frame a migration plan if the change affects live traffic
- Acceptance criteria are document-oriented ("design reviewed and approved") not code-oriented
Reference: ERA-1097
Implementation (from design)
For tickets that implement a design from a parent issue.
Sections: Context → Full manifest/code → Config changes → Platform considerations → Verification
Characteristics:
- Include complete, deployable YAML or code (not fragments)
- Reference the design document as the parent issue
- Call out platform-specific constraints (e.g., "GKE Autopilot does not support hostNetwork")
- Verification is staged: deploy alongside existing, migrate incrementally, validate, cut over
Reference: ERA-1098, ERA-1099
Observability / Monitoring
For adding metrics, alerts, and dashboards.
Sections: Context → What to monitor → Prometheus/alert config → Slack/PagerDuty routing → Verification
Characteristics:
- Include complete, deployable PodMonitor/ServiceMonitor YAML
- Include complete PrometheusRule YAML with alert expressions
- Specify severity levels and routing (critical → PagerDuty, warning → Slack)
- Include the exact PromQL expressions
- Verification includes a way to test the alert fires correctly
Reference: ERA-1104, ERA-1105, ERA-1106
Research / Evaluation
For tickets that produce a document or recommendation, not code.
Sections: Context → Evaluation criteria → Cost comparison template → Deliverable
Characteristics:
- Frame the evaluation as a structured comparison (table format)
- List specific questions to answer, not open-ended "investigate X"
- Include a cost comparison template with columns for each option
- Deliverable is a written document with a recommendation
Reference: ERA-1103
Project Structure
Project naming
Use the format: {Problem} — {Goal}
Example: LiveKit Connection Failure — Incident Resolution
Milestones
Milestones represent priority tiers with clear scope boundaries:
| Milestone | Naming Convention | Scope |
|---|---|---|
| P0 | P0 — {Immediate outcome} | Stop the bleeding. Config changes, no code if possible. |
| P1 | P1 — {Short-term goal} (timeframe) | Stabilize. Prevent recurrence. |
| P2 | P2 — {Long-term goal} (scale target) | Architecture changes for growth. |
Example milestones:
P0 — Immediate Fix (restore connectivity)P1 — Short-term Stability (this week)P2 — Scale Architecture (100K+ concurrent users)
Issue dependencies
Use Linear's blocking relationships to encode execution order:
ERA-1085 (remove turn_servers) ──blocks──▶ ERA-1086 (remove sidecar container)
ERA-1086 ──blocks──▶ ERA-1087 (remove kustomization references)
ERA-1087 ──blocks──▶ ERA-1089 (freeze deployments)
ERA-1089 ──blocks──▶ ERA-1090 (verify connectivity)
This ensures agents work in the correct order and don't start downstream tasks before upstream dependencies are complete.
Labels
Use labels to signal issue type for triage:
| Label | When to use |
|---|---|
Bug | Something is broken and needs fixing |
Improvement | Something works but should work better |
Feature | New capability being added |
research | Produces a document/recommendation, not code |
Priority
Map priority to urgency and blast radius:
| Priority | Meaning | Response time |
|---|---|---|
| Urgent (1) | Production broken, users affected now | Immediate |
| High (2) | Will cause problems soon or blocks Urgent work | Same day |
| Medium (3) | Important but not time-sensitive | This sprint |
| Low (4) | Nice to have, evaluate-and-decide | Backlog |
Formatting Rules
Code blocks
- Always use fenced code blocks with language identifiers (
yaml`,bash, ````python) - For YAML changes, show the complete block with surrounding context (not just the changed line)
- Use
# REMOVE,# ADD,# CHANGEcomments to mark what's changing - Show the "after" state as a standalone valid block that can be copied directly
Evidence
- Include actual log lines (not paraphrased) in fenced code blocks
- Include timestamps when showing sequences of events
- Bold the key signal in log output:
**allocs:0**,**removing participant without connection**
File references
- Always use relative paths from repo root:
era-ingress/k8s/base/statefulset.yaml - Include line numbers:
(lines 271-306) - When line numbers may drift, also include a unique string to search for
Tables
Use tables for:
- Comparing options (evaluation tickets)
- Test matrices (verification sections)
- Timeline reconstruction (context sections)
ASCII diagrams
Use ASCII diagrams for architecture context when the spatial relationship between components matters. Keep them under 20 lines. Mark broken components with [X] and working ones with checkmarks or clear labels.
Acceptance Criteria
Separate from Verification. Acceptance criteria are binary pass/fail conditions that define "done." Verification is the process of checking them.
Use atomic predicates, not prose:
## Acceptance criteria
- [ ] StatefulSet pods show 4/4 READY (not 5/5)
- [ ] `kubectl exec ... -- cat /etc/livekit/livekit.yaml` contains no `turn_servers` block
- [ ] `kustomize build` passes for both production and staging overlays
- [ ] iOS client connects with `connectionType: "turn"` in < 2s
- [ ] No `AllocateRequest` retransmission in LiveKit logs after 10 minutes of operation
Not:
## Acceptance criteria
Everything should work on all platforms and the config should be clean.
For non-functional requirements, make them measurable:
- [ ] Connection time < 2 seconds (p95)
- [ ] Memory usage per pod < 512Mi after sidecar removal
- [ ] Zero connection failures in 30-minute soak test
Process Instructions
When a ticket will result in a PR, include the expected workflow. This prevents agents from guessing branch naming, commit style, or deployment mechanics.
## Process
- **Branch**: `fix/remove-broken-sidecar-stunner` from `main`
- **Commit style**: `fix: <description>` (conventional commits)
- **PRs needed**: One to `main`, one to `develop`
- **Deployment**: ArgoCD syncs `k8s/` changes automatically (no CD pipeline trigger needed).
The CD pipeline has `paths-ignore: 'k8s/**'` — config-only changes do NOT
trigger a build. ArgoCD watches the repo directly.
- **Post-merge**: Watch `kubectl get pods -w` for rolling restart completion
This is especially important when the deployment mechanism is non-obvious. During the P0 implementation, the CD pipeline's paths-ignore: 'k8s/**' rule meant our config-only PR didn't trigger a build — ArgoCD handled it directly. Without documenting this, an implementer might wait indefinitely for a CI/CD pipeline that will never run.
Anti-Patterns
The vague ticket
## Description
Fix the TURN configuration so iOS connections work.
Missing: which file, what to change, why it's broken, how to verify.
The novel
A 2000-word ticket where the actual change is buried in paragraph 8. Lead with the change, provide context after.
The code-only ticket
Change line 35 of livekit-config.yaml from X to Y.
Missing: why this change is correct, what it fixes, how to verify it worked, what NOT to change nearby.
The assumed-context ticket
Same issue as last time — revert the STUNner change.
Every ticket must be self-contained. "Last time" means nothing to an agent picking this up in 3 months.
The omnibus ticket
One ticket that covers 5 unrelated changes. Each distinct change should be its own issue with its own verification steps. This enables parallel work and clear progress tracking.
The false-friend ticket
A ticket that names things to remove, where adjacent things share similar names but must be kept. Example: "Remove all stunner references" when ERA_TURN_PROVIDER=stunner must stay because it controls the external gateway, not the sidecar. Always use explicit NEVER boundaries to protect false friends.
The island ticket
A ticket that changes a resource without listing what else references it. Deleting a ConfigMap without checking if overlays patch it, removing a Service without checking if other UDPRoutes target it. Always include a Blast Radius section.
Checklist
Before submitting a ticket, verify:
- Context explains the causal chain (not just the symptom)
- File paths are exact and relative to repo root
- Code blocks show both "remove" and "add" states
- Blast radius lists all cross-file references to things being changed
- Boundaries explicitly state what to keep (NEVER) and what needs approval (ASK FIRST)
- Gotchas are called out (things that look related but shouldn't change)
- Rollback plan exists for production changes
- Acceptance criteria are binary pass/fail, not prose
- Verification has runnable commands or observable outcomes
- Process documents branch, commit, PR, and deployment expectations
- Dependencies are set up if execution order matters
- Labels and priority are assigned
- An agent with repo access but no prior context could implement this ticket without asking questions
Comment Scaffold
Progress comments posted to Linear issues follow structured templates to ensure consistency and traceability. All comments are posted by the agent or the orchestration pipeline — never manually.
Stage Transition Comment
Posted when work moves to a new phase (e.g., planning → coding, coding → reviewing).
## Stage: {previous} → {current}
### What was done
- [Bullet list of completed work in the previous stage]
### What's next
- [Bullet list of planned work in the upcoming stage]
### Decisions made
- [Optional — any judgment calls or trade-offs selected]
### Risks/blockers
- [Optional — anything that might prevent progress]
Completion Comment
Posted when an issue moves to Done.
## Result: Completed ✓
**Duration**: {time from In Progress to Done}
### Summary of changes
- [Bullet list of what was changed, created, or removed]
### Blast radius
- [Files, services, or overlays affected by these changes]
### Verification performed
- [Tests run, commands executed, manual checks done]
### PR/artifacts
- PR: {link}
- Branch: `{branch-name}`
Failure Comment
Posted when work fails and cannot be completed.
## Result: Failed ✗
**Failed at stage**: {stage where failure occurred}
### What was attempted
- [Bullet list of actions taken before failure]
### Root cause of failure
- [Clear explanation of why it failed]
### Recommendations
- [What should be done differently on retry]
Comment Frequency Guidelines
- Minimum: One comment per stage transition
- Long-running stages: Post an interim update every 30 minutes
- Errors: Post immediately on any error, even if recoverable
- Empty comments: Never post a comment that says only "working on it" or "in progress" — every comment must contain specific information
Status Transition Criteria
State transitions must follow binary criteria. Do not move an issue to the next state until all criteria are met.
Issues
| Transition | Criteria |
|---|---|
| Backlog → Todo | Fully specified per checklist; dependencies identified |
| Todo → In Progress | Branch created or agent session started |
| In Progress → In Review | PR opened; tests passing; completion comment posted |
| In Review → Done | PR merged; verification confirmed; completion comment with PR link |
Projects
| Transition | Criteria |
|---|---|
| Planned → In Progress | First issue moved to In Progress |
| In Progress → Completed | All issues Done or Cancelled; summary comment posted on project |
Milestones
Milestones have no formal Linear state, but apply these conventions:
- Post a summary comment on the parent project when the last issue in a milestone completes
- Note target date slips when they occur — include original date, new date, and reason for slip
- When all milestone issues are Done, update the milestone description with a completion summary
Progress Tracking Guidelines
This table maps events to the actions an agent (or human) should take on the Linear issue:
| Event | Linear Action |
|---|---|
| Pick up issue | Move to In Progress; post stage transition comment (none → planning) |
| Stage transition | Post stage transition comment with what was done and what's next |
| Review requested | Move to In Review; post comment with PR link and test results |
| Completion | Move to Done; post completion comment with full scaffold |
| Failure | Post failure comment with root cause; leave in current state for triage |
| Blocker encountered | Post comment describing the blocker; add blocked label if available |
| Scope change | Post comment describing what changed and why; update ticket description |
Quality Criteria for Progress Comments
- Specific over vague: "Fixed null check in
parseConfig()at line 42" not "fixed a bug" - Evidence over assertions: "Tests pass: 47/47 green, 0 flaky" not "tests pass"
- Forward-looking: Every comment should indicate what happens next
- Self-contained: A reader should understand the comment without reading previous comments
Sources
This guide draws from:
- Direct experience: ERA-1085–ERA-1108 incident resolution (24 issues, P0 resolved in one session)
- Anthropic: Effective Context Engineering for AI Agents — context prioritization, just-in-time retrieval, progressive disclosure
- GitHub: How to write a great agents.md — three-tier boundary system (Always/Ask First/Never), 2500-repo analysis
- Addy Osmani: How to write a good spec for AI agents — spec-driven development, modular phased specs, instruction volume degradation
- SWE-bench: Structured task descriptions improve agent resolution rates; vague descriptions cause agents to "lose focus or fall into repetitive, unproductive loops"
- Agentic PRD: Designing Requirements for AI-Driven Engineering — I/O examples as ground truth anchors, binary acceptance criteria
- Linear: Agent Interaction Guidelines — agent identity, transparency, feedback, human accountability