-
Notifications
You must be signed in to change notification settings - Fork 14
Description
🤖 Kelos Strategist Agent @gjkim42
Area: Integration Opportunities
Summary
Kelos currently supports four source types: githubIssues, githubPullRequests, cron, and jira. The Jira integration (added in PR #556) proved the pattern for non-GitHub issue trackers works cleanly — the Source interface (internal/source/source.go:37) maps naturally to any system that produces work items. However, a major gap remains: Linear, the issue tracker most popular among the exact developer demographic most likely to adopt Kelos (startup and scale-up engineering teams running Kubernetes).
This proposal adds a linear source type to TaskSpawner, enabling teams that use Linear for project management to drive autonomous AI agents from their existing workflow — without migrating to GitHub Issues or Jira.
Why Linear specifically?
1. Market alignment with Kelos's target audience
Linear's user base heavily overlaps with Kelos's ideal adopters:
- Kubernetes-native teams: Linear is dominant among startups and scale-ups that already run on Kubernetes
- Automation-minded engineers: Linear users tend to be early adopters of developer tooling and AI-assisted workflows
- Fast-moving teams: Linear's speed-focused design attracts teams that would benefit most from autonomous agents
Jira targets enterprise; GitHub Issues targets open-source. Linear targets the growth-stage engineering teams that are currently underserved by Kelos's source types.
2. Significant adoption numbers
Linear has crossed 10,000+ companies including many well-known engineering organizations. Among YC-backed startups and developer-tools companies, Linear is often the default choice over Jira.
3. Clean API that maps directly to the Source interface
Linear provides a well-documented GraphQL API that maps cleanly to Kelos's WorkItem struct:
| Linear concept | WorkItem field | Notes |
|---|---|---|
issue.identifier (e.g., ENG-42) |
ID |
Unique within workspace |
issue.number |
Number |
Numeric identifier |
issue.title |
Title |
Issue title |
issue.description |
Body |
Markdown description |
issue.url |
URL |
Linear web URL |
issue.labels[].name |
Labels |
Linear labels |
issue.comments[].body |
Comments |
Comment text |
"Issue" |
Kind |
Always "Issue" for Linear |
4. Comment-based workflow control works identically
Linear supports comments on issues, so Kelos's existing commentPolicy pattern (triggerComment, excludeComments, allowedUsers) translates directly. A team can use /kelos trigger comments in Linear just like they do in GitHub Issues.
Proposed API
Add linear to the When struct
type When struct {
GitHubIssues *GitHubIssues `json:"githubIssues,omitempty"`
GitHubPullRequests *GitHubPullRequests `json:"githubPullRequests,omitempty"`
Cron *Cron `json:"cron,omitempty"`
Jira *Jira `json:"jira,omitempty"`
// NEW
Linear *Linear `json:"linear,omitempty"`
}New Linear source type
// Linear discovers issues from a Linear team and spawns one Task per matched issue.
// Authentication uses a Linear API key provided via secretRef.
type Linear struct {
// TeamKey is the Linear team identifier (e.g., "ENG", "PLATFORM").
// Issues are discovered from this team only.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
TeamKey string `json:"teamKey"`
// States filters issues by workflow state names (e.g., ["Todo", "In Progress"]).
// When empty, issues in all non-terminal states are discovered.
// +optional
States []string `json:"states,omitempty"`
// Labels filters issues that have ALL of the specified label names.
// When empty, no label filtering is applied.
// +optional
Labels []string `json:"labels,omitempty"`
// ExcludeLabels excludes issues that have ANY of the specified labels.
// +optional
ExcludeLabels []string `json:"excludeLabels,omitempty"`
// Assignee filters issues assigned to a specific user (Linear display name or email).
// When empty, no assignee filtering is applied.
// +optional
Assignee string `json:"assignee,omitempty"`
// Priority filters issues by Linear priority level.
// Linear priorities: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low.
// When set, only issues at this priority or higher (lower number) are discovered.
// +optional
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=4
MaxPriority *int `json:"maxPriority,omitempty"`
// Project filters issues belonging to a specific Linear project name.
// When empty, issues from all projects within the team are discovered.
// +optional
Project string `json:"project,omitempty"`
// CommentPolicy configures comment-based workflow control.
// Works identically to GitHub Issues commentPolicy.
// +optional
CommentPolicy *LinearCommentPolicy `json:"commentPolicy,omitempty"`
// Reporting configures status reporting back to Linear issues.
// When enabled, the spawner posts comments on issues when tasks start/complete.
// +optional
Reporting *LinearReporting `json:"reporting,omitempty"`
// SecretRef references a Secret containing a "LINEAR_API_KEY" key.
// The API key must have read access to the team's issues.
// Create one at: Linear Settings → API → Personal API keys.
// +kubebuilder:validation:Required
SecretRef SecretReference `json:"secretRef"`
// PollInterval overrides spec.pollInterval for this source (e.g., "30s", "5m").
// When empty, spec.pollInterval is used.
// +optional
PollInterval string `json:"pollInterval,omitempty"`
}
// LinearCommentPolicy mirrors GitHubCommentPolicy for Linear issues.
type LinearCommentPolicy struct {
// TriggerComment is the command that must appear in a comment to include the issue.
// When set, only issues with a matching comment are discovered.
// +optional
TriggerComment string `json:"triggerComment,omitempty"`
// ExcludeComments lists commands that exclude an issue from discovery.
// +optional
ExcludeComments []string `json:"excludeComments,omitempty"`
}
// LinearReporting configures status comments posted back to Linear issues.
type LinearReporting struct {
// Enabled controls whether the spawner posts status comments.
// +kubebuilder:default=false
Enabled bool `json:"enabled"`
}Implementation approach
Source implementation (internal/source/linear.go)
Linear's GraphQL API makes the implementation straightforward. The core query:
query($teamKey: String!, $after: String) {
team(key: $teamKey) {
issues(
first: 50
after: $after
filter: {
state: { name: { in: $states } }
labels: { name: { in: $labels } }
}
) {
nodes {
identifier
number
title
description
url
priority
state { name }
assignee { name email }
labels { nodes { name } }
comments { nodes { body user { name } } }
project { name }
}
pageInfo {
hasNextPage
endCursor
}
}
}
}The LinearSource struct follows the same pattern as JiraSource:
type LinearSource struct {
TeamKey string
States []string
Labels []string
ExcludeLabels []string
Assignee string
MaxPriority *int
Project string
CommentPolicy *LinearCommentPolicy
APIKey string
Client *http.Client
}
func (s *LinearSource) Discover(ctx context.Context) ([]WorkItem, error) {
issues, err := s.fetchAllIssues(ctx)
if err != nil {
return nil, err
}
var items []WorkItem
for _, issue := range issues {
// Apply client-side filters (excludeLabels, assignee, priority, project)
if s.shouldExclude(issue) {
continue
}
items = append(items, WorkItem{
ID: issue.Identifier, // e.g., "ENG-42"
Number: issue.Number,
Title: issue.Title,
Body: issue.Description,
URL: issue.URL,
Labels: extractLinearLabels(issue),
Comments: extractLinearComments(issue),
Kind: "Issue",
})
}
return items, nil
}Spawner wiring (cmd/kelos-spawner/main.go)
Add --linear-api-key-file flag and extend buildSource():
case ts.Spec.When.Linear != nil:
lin := ts.Spec.When.Linear
apiKey, err := readLinearAPIKey(linearAPIKeyFile)
if err != nil {
return nil, err
}
return &source.LinearSource{
TeamKey: lin.TeamKey,
States: lin.States,
Labels: lin.Labels,
ExcludeLabels: lin.ExcludeLabels,
Assignee: lin.Assignee,
MaxPriority: lin.MaxPriority,
Project: lin.Project,
CommentPolicy: convertLinearCommentPolicy(lin.CommentPolicy),
APIKey: apiKey,
Client: httpClient,
}, nilController changes (internal/controller/taskspawner_deployment_builder.go)
Add Linear secret volume mount and spawner arguments, following the same pattern as Jira:
if ts.Spec.When.Linear != nil {
args = append(args, "--linear-api-key-file=/secrets/linear/LINEAR_API_KEY")
volumes = append(volumes, linearSecretVolume(ts.Spec.When.Linear.SecretRef))
}Files to modify
| File | Change |
|---|---|
api/v1alpha1/taskspawner_types.go |
Add Linear struct, LinearCommentPolicy, LinearReporting, When.Linear field |
internal/source/linear.go |
New: LinearSource implementing Source interface (~200 lines) |
internal/source/linear_test.go |
New: Unit tests with mock GraphQL responses (~300 lines) |
cmd/kelos-spawner/main.go |
Add --linear-api-key-file flag, extend buildSource() |
cmd/kelos-spawner/main_test.go |
Add buildSource test for Linear config |
internal/controller/taskspawner_deployment_builder.go |
Add Linear secret volume mount and spawner args |
internal/controller/taskspawner_controller.go |
No changes needed (Linear uses polling like Jira) |
Example TaskSpawner configs
Example 1: Engineering team bug triage agent
apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
name: linear-bug-triage
spec:
when:
linear:
teamKey: ENG
states: ["Triage"]
labels: ["bug"]
secretRef:
name: linear-api-key
pollInterval: "5m"
maxConcurrency: 3
taskTemplate:
type: claude-code
model: sonnet
credentials:
type: oauth
secretRef:
name: claude-credentials
workspaceRef:
name: main-app
branch: "fix/{{.ID}}"
ttlSecondsAfterFinished: 3600
promptTemplate: |
Linear issue {{.ID}}: {{.Title}}
Description:
{{.Body}}
Labels: {{.Labels}}
Instructions:
1. Analyze the bug report and reproduce the issue
2. Identify the root cause
3. Implement a fix with tests
4. Create a PR titled "Fix: {{.Title}}"Example 2: Feature spec to implementation
apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
name: linear-feature-impl
spec:
when:
linear:
teamKey: PLATFORM
states: ["Ready for Development"]
maxPriority: 2 # Urgent and High priority only
commentPolicy:
triggerComment: "/kelos implement"
secretRef:
name: linear-api-key
maxConcurrency: 2
taskTemplate:
type: claude-code
credentials:
type: oauth
secretRef:
name: claude-credentials
workspaceRef:
name: platform-service
branch: "feature/{{.ID}}"
ttlSecondsAfterFinished: 7200
promptTemplate: |
Implement the feature described in Linear issue {{.ID}}.
Title: {{.Title}}
Priority: High/Urgent
Specification:
{{.Body}}
Discussion:
{{.Comments}}
Create a PR with the implementation. Include tests.Example 3: Cron + Linear for stale issue cleanup
apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
name: linear-stale-review
spec:
when:
linear:
teamKey: ENG
states: ["In Progress"]
labels: ["needs-review"]
reporting:
enabled: true
secretRef:
name: linear-api-key
pollInterval: "24h"
maxConcurrency: 5
taskTemplate:
type: claude-code
model: haiku
credentials:
type: api-key
secretRef:
name: anthropic-key
workspaceRef:
name: main-app
promptTemplate: |
Review the status of Linear issue {{.ID}}: {{.Title}}
Current state: In Progress, labeled "needs-review"
Description: {{.Body}}
Check if there is an associated PR branch. If so, review
the code changes and post a summary comment on the issue.
If no branch exists, comment noting the issue appears stale.Why this is strategically important for Kelos
-
Opens a new market segment: Linear-using teams (thousands of companies) are currently excluded from issue-driven Kelos workflows. This is arguably a larger addressable market than Jira for Kelos's typical user profile.
-
Low implementation cost, high adoption potential: The Jira source implementation (
internal/source/jira.go) is ~270 lines. A Linear source would be similar in size. TheSourceinterface and spawner architecture already handle all the orchestration complexity. -
Reinforces Kelos as platform-agnostic: With GitHub, Jira, and Linear support, Kelos becomes the clear choice for "bring your own issue tracker" agent orchestration. This positions Kelos ahead of tools that only integrate with GitHub.
-
Complements the GitLab proposal (Integration: Add GitLab issues and merge requests as TaskSpawner source types for multi-platform agent workflows #701): Together with GitLab support, Linear support would cover the three most common toolchains:
- GitHub + GitHub Issues → already supported
- GitHub/GitLab + Jira → Jira source
- GitHub/GitLab + Linear → this proposal
- GitLab + GitLab Issues → Integration: Add GitLab issues and merge requests as TaskSpawner source types for multi-platform agent workflows #701
Relationship to existing issues
| Issue | Relationship |
|---|---|
| #701 (GitLab source) | Complementary: GitLab is a code hosting integration; Linear is a project management integration. Together they complete the non-GitHub toolchain story. |
| #687 (Webhook source) | Alternative trigger path: Linear supports webhooks. A generic webhook source could theoretically receive Linear events, but a native source provides filtering, comment policy, reporting, and a better UX. |
| #595 (Slack source) | Different layer: Slack is for ChatOps-style ad-hoc commands; Linear is for structured issue-driven workflows. Different use cases. |
| #664 (githubRepositories) | Independent: Repository discovery vs. issue tracking are orthogonal dimensions. |
/kind feature