herdr-linear-agent is a Herdr plugin that picks up Linear issues delegated to its Linear app user and works on them with AI coding agents running in Herdr panes.
Note
herdr-linear-agent is not Linear Agent, the assistant built into Linear (@Linear, ⌘J). It is an independent Herdr plugin, not made by or affiliated with Linear, that connects to your workspace as its own OAuth app through Linear's public Agents API, the same way other third-party agents do.
For each issue, the plugin starts one coordinator agent. The coordinator reads the issue, splits the work, and starts one worker agent per repository in a separate Git worktree. The coordinator and its workers occupy tabs of one Herdr workspace. Workers implement the change, open pull requests and check CI. The plugin reports progress, questions and results to the issue's Agent Session in Linear, and people reply there.
The run workspace is labeled acme/DATA-1 · Fix the API. Its coordinator tab is named coordinator; worker tabs are named w1 · api · Add the endpoint. Worker checkouts live under $XDG_STATE_HOME/herdr-linear-agent/worktrees/<workspace>/<ISSUE-KEY>/<id>-<repository>. The plugin uses Git directly to create them. Existing workers in separate Herdr workspaces move into the run workspace when explicitly restarted; existing open workspaces are not renamed. Moving the coordinator into another workspace pauses new placements and reports a layout conflict. Move it back into the run workspace to continue.
- Linear is the front door. Delegate an issue to the app user; talk to the run in the issue's Agent Session. The plugin opens no endpoint and polls Linear; the webhook Linear requires can point at an endpoint that discards what it receives (see Set up).
- One writer. Only the plugin's background ticker writes to Linear. Agents call plugin subcommands that queue requests; they never hold a Linear token.
- Profiles, not flags. Agent kind, model, effort and permission flags live in profiles you write in the config. Agents choose a profile by name and nothing else.
- Limits enforced by the binary. Concurrent runs, workers per run and total agents are capped by the plugin, not by the agents.
- Merging is opt-in. A team can use one-time human authorization with
merge_on_authorization = true, or a headless decision profile withmerge_decision = { profile = "merge-risk", max_risk_score = 20 }. These modes are mutually exclusive. The machine score is a risk signal, not a user's approval: the coordinator must still invokeworker merge, and the plugin revalidates the PR and evidence before making one SHA-bound squash-merge attempt. Confirmed merges move the issue to deployment. The plugin never force-pushes.
- macOS or Linux, and Herdr 0.9.1 or later
- On Linux: a Secret Service provider (GNOME Keyring, KWallet) for the token, and
xdg-open - The agent CLIs your profiles use (
claude,codex,cursor-agent, ...) and Git 2.17 or later ghon the ticker'sPATHwhen any team enables either merge mode- One or more Linear workspaces where you can install an OAuth application (workspace admin)
-
Create a Linear OAuth application (Settings → API → OAuth applications) for this plugin, in each workspace you use. Its name becomes the app user's name: pick one that people cannot mistake for Linear's own
@Linear(for exampleherdr-linear-agent). Add the callback URLhttp://127.0.0.1:43871/oauth/callbackand note the client ID. The plugin uses the authorization code flow with PKCE and never needs the client secret. It installs the app withactor=appand the scopesread,writeandapp:assignable, which creates an app user you can delegate issues to.Add a webhook with the "Agent session events" category, then disable its delivery (required). Linear enables Agent Sessions for an app only when it subscribes to that category, and without them the plugin cannot pick up any issue ("Agent sessions are not enabled for this application"). The plugin never reads webhooks, since it polls Linear, so nothing needs to receive them: enter any URL, for example
https://example.invalid/linear, and disable the webhook's delivery so no issue or session content is sent anywhere. Keep the webhook and its category; only the delivery is off. Add it before you log in; an app that was authorized before needs to log in again before the subscription takes effect. -
Install the plugin:
herdr plugin install civitaspo/herdr-linear-agent
The build step downloads the release binary, or builds from source with
cargowhen there is none. -
Write the config at
$XDG_CONFIG_HOME/herdr-linear-agent/config.toml(default~/.config/herdr-linear-agent/config.toml), and one folder per agent profile underprofiles/next to it. See Configuration and Profiles. -
Log in: run the Herdr action herdr-linear-agent: log in to Linear. For each workspace without a stored token, it opens the browser, stores the token in the macOS Keychain or, on Linux, the Secret Service (service
dev.herdr-linear-agent.linear.oauth.v1, one account per workspace named after it), and checks that it acts as the app user; when every workspace has a token, it logs in to all of them again.herdr-linear-agent action login --workspace <name>logs in to one workspace again. The browser must run on the same machine: Linear redirects to127.0.0.1. -
Check the setup: run herdr-linear-agent: check setup.
-
Delegate an issue in one of the configured teams to the app user, as a person in the team's
allowed_delegator_ids.
# development_mode = false # opt in to development commands; see Development mode
[workspaces.acme] # a name for the workspace: lower-case letters, digits and `-`
client_id = "your-oauth-client-id" # the OAuth application of this workspace
# callback_port = 43871 # the port of the callback URL you registered
# intake_interval_seconds = 5 # how often delegated issues are polled
# run_read_interval_seconds = 5 # how often active runs are read
[workspaces.acme.teams.DATA] # a team to pick issues from, by team key
allowed_user_ids = ["linear-user-uuid"] # whose replies reach the coordinator
# allowed_delegator_ids = ["linear-user-uuid"] # whose delegations are taken (default: allowed_user_ids)
started_state = "In Progress" # where issues move on pickup (default)
review_state = "In Review" # where the first `finish` moves the issue (default)
# deploy_state = "In Deploy" # after a worker merge is confirmed
# postmortem_state = "In Postmortem" # when the coordinator finishes deployment work
# done_state = "Done" # after a successful workflow postmortem
# merge_on_authorization = false # human mode: require an allowed Linear reply
# merge_decision = { profile = "merge-risk", max_risk_score = 20 } # alternative headless assessment mode
routing = "default" # the routing of this team's issues, a table under [routing] (required)
[workspaces.acme.teams.OPS] # each team has its own allowed users, review state and routing
allowed_user_ids = ["another-user-uuid"]
review_state = "Ready for review"
routing = "default"
[workspaces.other] # another workspace, with its own OAuth application
client_id = "another-oauth-client-id"
[workspaces.other.teams.DATA] # the same team key as in `acme` is fine
allowed_user_ids = ["linear-user-uuid-in-other"]
routing = "default"
[herdr]
session = "default" # the Herdr session runs start in; omit for the default session
[limits] # defaults shown
max_runs = 2
max_workers_per_run = 4
max_agents = 8
ask_to_continue_after_hours = 8 # after this many hours a run asks whether to go on
routing_agent_timeout_seconds = 120 # how long a routing agent may take, unless its profile says
postmortem_agent_timeout_seconds = 300 # how long a postmortem agent may take, unless its profile says
[notifications]
herdr = true # also show a Herdr notification when a run needs a person
[claude]
auto_accept_trust_dialog = false # true: accept Claude Code's trust dialog for the plugin's folders (see below)
[repositories.api]
path = "/Users/me/src/github.com/acme/api"
base = "main" # workers branch from origin/<base>; never guessed
description = "The API server"
[repositories.api.worktree] # optional trusted setup and teardown
copy = [".env", "apps/web/.env.local"] # regular files, never overwrite existing destinations
symlink = ["node_modules"] # files or directories in the main checkout
post_create = ["mise trust", "pnpm install"]
post_create_timeout_seconds = 600 # per command, 1..86400
post_remove = [] # commands in the main checkout after removal
post_remove_timeout_seconds = 600 # per command, 1..86400
[routing.default] # a routing; teams name one (see Coordinator routing)
agent = "router" # the profile of the routing agent; needed only when a list below has several profiles
coordinators = ["coordinator", "coordinator-light"] # the coordinator profiles it picks from
postmortems = ["postmortem"] # the profiles that may write postmortems (see Postmortems); none writes none
workers = ["standard", "deep"] # the profiles a coordinator may start workers with
# Optional local supervisor. Omit this and [routing.default.generated] to disable it.
[supervisor]
profile = "supervisor" # ordinary profile controlling its kind, model, arguments and instructions
max_generated_profiles_per_routing = 5 # defaults to 5; allowed range is 1 to 20
[routing.default.generated]
workers = ["standard"] # approved manual bases; generated variants inherit only from theseReloading the config. The running ticker keeps the config it started with until you run the Herdr action herdr-linear-agent: reload the config. The action checks the config first and shows why it does not load; when it loads, the ticker starts its tasks again with it, as a ticker restart would, and reads the Linear credentials again, so the macOS Keychain may ask once more. The ticker also compares config.toml and the profile folders with the ones it runs on every second, by content, and when they changed it shows a Herdr notification (if notifications.herdr is on) asking you to reload, or saying why the new files do not load; it tells once per change. A reload takes effect for what is decided and started afterwards: routing of new issues, coordinator and worker launches and restarts, limits and notifications.
- An agent that is running keeps its arguments and instructions; the change reaches it at its next launch or restart.
AGENTS.mdand briefs already written are not rewritten. - Removing a profile that a running run uses is not refused; restarting or resuming with it fails then with the usual unknown-profile error.
- Lowering a limit below the current count leaves running runs alone; new ones wait until there is room.
Workspaces and teams. Each workspace polls Linear with its own OAuth application, token and app user. Runs are named <workspace>/<ISSUE-KEY>, for example acme/DATA-1, so the same issue key in two workspaces makes two runs; the name is what agents pass to herdr-linear-agent commands, and the workspace is part of agent names (acme-data-1-coordinator), branches (herdr-linear-agent/acme/data-1/w1-...) and brief folders. A team's allowed_user_ids, allowed_delegator_ids and workflow states apply to that team's issues only. A run whose team was removed from the config keeps running, relays nobody's replies and uses the default workflow states.
Workflow states. The plugin moves issues through the configured started, review, deployment, postmortem and completed states. State names are matched case-insensitively. If a target is missing, the plugin asks an allowed user in the Linear session which team state to use for that transition. The answer applies to that transition only, so a later missing target asks again. The completed target must have Linear's completed type. If you call the deployment phase "In Release", set deploy_state = "In Release".
Who may delegate. The ticker takes an issue only when a person in the team's allowed_delegator_ids delegated it; see Security notes, and Finding user IDs for the IDs. Without the key, the team's allowed_user_ids may delegate. Set it apart when someone should start runs without their replies counting as instructions, or the other way round.
To improve how the agents work run after run, a routing can list postmortem profiles. After a worker PR merge is confirmed, the coordinator completes deployment work and calls finish. The issue moves to its postmortem state, and a postmortem agent reads the run. The plugin posts the summary and labels, then moves the issue to its completed state only after that postmortem succeeds. Without a postmortem profile, or after a failed postmortem, the issue stays in its postmortem state for manual follow-up. Finishing into review does not run a postmortem.
An issue completed or canceled before a successful workflow postmortem gets a final postmortem when its run closes. A successful workflow postmortem prevents a duplicate final one. A postmortem profile is an ordinary profile: its instructions.md is the method, and says which labels to add. Edit it as often as you like; each comment names the profile and the method's version.
# profiles/postmortem/config.toml: any kind that can be a routing agent
kind = "claude"
model = "haiku"
description = "Reviews a run for what to change in how the agents work"<!-- profiles/postmortem/instructions.md -->
- What was asked, and what was delivered.
- What was smooth, and where time went (retries, failed commands, waiting for people).
- What to change next time, concretely.
Add the label `Improvement` only when there is something concrete to change in how the agents work.Then list it in the routing: postmortems = ["postmortem"]. Each postmortem, workflow or final, picks its profile when it is written: a list with one profile gives that profile; with several, the routing agent picks one from the run's issue, conversation.md, workers' reports, PRs and times (not the transcripts), and a failed pick gives the first. The picked profile runs headless the way the routing agent does, with no tools: the plugin gives it the run's records on standard input (the issue, conversation.md, the workers' reports, and the agents' transcripts, long ones shortened in the middle), and it answers with the summary and the labels. The plugin keeps each answer in the run folder (.state/postmortems/), posts the comment, and adds the labels that exist in Linear (it creates none). A workflow comment reads **Postmortem (workflow)**, method postmortemversion<12 hex digits>``, then the summary; the version is a hash of the profile, so it changes when you edit the method.
The optional supervisor reviews saved postmortem outcomes and keeps local, workspace/team/routing-scoped knowledge for future decisions. It does not read Linear credentials or write to Linear. The plugin remains the only Linear writer. The supervisor's model, kind, arguments and method come from an ordinary profile you own. Its managed Herdr space uses the exact label herdr-linear-agent-supervisor; one agent slot is reserved while it is enabled. Its durable reports, decisions, Markdown knowledge and rebuildable SQLite search index live under the plugin state directory.
To enable it:
- Create
profiles/supervisor/config.tomlandinstructions.md, as below. The supervisor runs interactively in Herdr and needs permission to invoke the plugin's local supervisor commands. - Add
[supervisor]toconfig.tomland setprofile = "supervisor". Keeplimits.max_agentsat least 3, including the supervisor's reserved slot. - Optionally allow generated profiles with
[routing.<name>.generated], then run herdr-linear-agent: reload the config. The ticker starts and manages the supervisor in the configured Herdr session. Development mode is independent of this feature.
| Setting | Meaning |
|---|---|
supervisor.profile |
Required name of the ordinary profile used for the supervisor agent. Omitting [supervisor] disables the feature. |
supervisor.max_generated_profiles_per_routing |
Maximum active generated variants per workspace/team/routing scope, across roles. Defaults to 5; accepts 1–20. |
routing.<name>.generated.coordinators |
Manual bases allowed for generated coordinators; defaults to []. Each must also appear in that routing's coordinators. |
routing.<name>.generated.workers |
Manual bases allowed for generated workers; defaults to []. Each must also appear in that routing's workers. |
routing.<name>.generated.postmortems |
Manual bases allowed for generated postmortem agents; defaults to []. Each must also appear in that routing's postmortems. |
Empty generated lists keep the supervisor limited to knowledge updates. Nonempty generated coordinator or postmortem lists require a routing agent, even with one manual candidate. A generated profile can change its description and instruction file, and can inherit from another listed base for its role. It cannot change kind, model, effort, arguments, environment or timeout. Publishing a new revision changes future candidate lists after the supervisor's decision, while an already pinned run keeps its resolved profile. Removing a role or base from config blocks new starts and resumes that depend on it. Retiring a variant removes it from future runs while preserving existing pins. Editing a manual base makes its old generated variants ineligible for new runs until republished against that base.
The supervisor receives bounded batches of saved reports and commits a decision and portable memory checkpoint before those reports are acknowledged. A decision may update knowledge, publish or retire one variant, or record no change. Legacy postmortems without saved routing attribution are preserved for inspection but excluded from decisions. The derived search index can be rebuilt from the committed Markdown and JSON files; it is not the source of truth.
# profiles/supervisor/config.toml
kind = "claude"
model = "sonnet"
effort = "high"
args = ["--permission-mode", "auto"]
description = "Reviews completed runs and maintains scoped agent guidance"<!-- profiles/supervisor/instructions.md -->
Use `herdr-linear-agent supervisor context --generation <generation>` to read the current batch.
Record a durable decision with `supervisor decide --generation <generation> --decision-file <path>`.
Use report and topic handles for evidence. Do not infer success from issue state or labels.Inspect and manage it. herdr-linear-agent: status (action status) shows its lifecycle status, generation, memory/catalog versions, unacknowledged reports and recovery details. herdr-linear-agent: check setup (action doctor) also reports supervisor or catalog problems. ticker status checks only the ticker process.
All commands below start with herdr-linear-agent supervisor. They use local state and the workspace/team/routing scopes currently allowed by the config, require [supervisor], and print JSON except for the export confirmation.
| Command suffix | Purpose |
|---|---|
context --generation <n> |
Issues or resumes a bounded report batch, with evidence, checkpoint, knowledge and profile policies. Used by the supervisor with the generation in its ticker prompt; this changes batch state. |
decide --generation <n> --decision-file <path> |
Validates and commits a decision JSON file, acknowledges the batch and prints a durable receipt. Use - to read standard input. |
memory search "<query>" |
Searches committed knowledge in the current scopes; rebuilds a missing or stale search index. |
show <handle> |
Reads report:<id>, decision:<id> or topic:<id>[@<revision>]. Use IDs returned by context, search or a receipt. |
profiles list |
Lists committed generated revisions and marks each current catalog entry. |
profiles show <id> [--revision <n>] |
Reads one revision; defaults to the highest committed revision for that ID. |
profiles retire <id> --expected-revision <n> --reason "<text>" --source-report <id> |
Creates a retired revision, removing the variant from future candidates. Supply one to five source reports; repeat --source-report for additional evidence. |
profiles rollback <id> --revision <n> --reason "<text>" |
Republishes retained content as a new revision of an active variant, subject to current base permissions. |
profiles export <id> --revision <n> --name <manual-name> --output <new-directory> |
Writes an ordinary profile to a new directory after validating its base and resolved behavior. |
Retirement and rollback create new immutable catalog revisions through the same commit path as supervisor decisions. Export writes config.toml, instructions.md and a short README.md directly into a new destination directory; it does not activate the exported profile or change routing. Review and deploy that ordinary profile through your existing config process, then retire the generated variant separately if wanted. Local filesystem access is the operator boundary; command allow-lists are not an operating-system sandbox.
Reload or disable it. Changing the supervisor profile drains an issued batch for up to two minutes before replacing the managed workspace and native agent session. Changing the Herdr session fences outstanding decisions and closes the previous workspace before replacement. To disable the supervisor, remove [supervisor] and every [routing.<name>.generated] section, then reload. The ticker closes its managed workspace when Herdr is reachable and retains durable evidence. Supervisor commands require the feature to be configured, so inspect or export needed revisions before disabling it.
See the supervisor command and decision reference for the JSON format, receipts, stale-generation handling and recovery details.
allowed_user_ids and allowed_delegator_ids take Linear user IDs (UUIDs), not names or emails. Three ways to find one:
- From the ticker. When the ticker declines a delegation or ignores a reply, it writes the person's name and ID to
ticker.logand shows them in a Herdr notification, for exampleacme/DATA-1: not picked up: delegated by Jane Doe (6b7b1cde-...), who is not in allowed_delegator_ids of team DATAoracme/DATA-1: ignored a reply from Jane Doe (6b7b1cde-...); add the id to allowed_user_ids of team DATA to let it through. Copy the ID into the config and run herdr-linear-agent: reload the config. - From an ignored reply. A run's
.state/ignored-prompts.mdlists each ignored reply with its sender's ID. - From the Linear API. With a personal API key, post to
https://api.linear.app/graphql: the query{ viewer { id name } }gives your own ID, and{ users(filter: { email: { eq: "jane@example.com" } }) { nodes { id name } } }another person's.
Profiles are not in config.toml. Each one is a folder next to it, named after the profile, so you can hand a profile to other people as it is: copy the folder, or link it from a repository of shared profiles. A [profiles] table in config.toml is refused.
~/.config/herdr-linear-agent/
├── config.toml
└── profiles/
├── coordinator/
│ └── config.toml
├── coordinator-light/
│ ├── config.toml
│ └── instructions.md # optional
├── router/
│ └── config.toml
├── standard/
│ └── config.toml
├── deep/
│ └── config.toml
└── merge-risk/
├── config.toml
└── instructions.md
# profiles/coordinator/config.toml
kind = "claude"
model = "opus"
effort = "high"
args = ["--permission-mode", "auto", "--disallowed-tools=Agent"] # no subagents of its own (see below)
description = "The default coordinator"# profiles/coordinator-light/config.toml
kind = "claude"
model = "sonnet"
effort = "medium"
args = ["--permission-mode", "auto", "--disallowed-tools=Agent"]
description = "Small, well-scoped issues that one worker can finish"<!-- profiles/coordinator-light/instructions.md -->
Start one worker. Ask in the session before you split the work.# profiles/router/config.toml: the routing agent, any registered kind, a small fast model
kind = "claude" # or another kind from "Routing agent kinds" below
model = "<a small model of that kind>"# profiles/standard/config.toml
kind = "claude"
model = "sonnet"
effort = "high"
args = ["--permission-mode", "auto"]
description = "Scoped features and fixes"# profiles/deep/config.toml
kind = "codex"
model = "gpt-6-sol"
effort = "xhigh"
args = ["-s", "workspace-write"]
description = "Changes across modules, bugs with an unknown cause"# profiles/merge-risk/config.toml: one supported headless kind
kind = "codex"
model = "gpt-6-sol"
effort = "high"<!-- profiles/merge-risk/instructions.md -->
Assess the complete change and supplied evidence. Return a risk score from 0 to 100, where higher means more risk or uncertainty, and a concise evidence-based rationale.A profile's config.toml takes kind, model, effort, args, description, base and, for routing, postmortem and merge decision agents only, env and timeout_seconds (how long a call may take, 1 to 3600 seconds; the limits' default for the role when unset). A merge decision profile must use a supported headless kind and have non-empty instructions.md. The plugin never reads or injects Linear OAuth credentials; it removes its config/state path variables and runs the CLI through the kind's headless recipe with no tools. The CLI keeps the current user's normal login and home context, as described under Routing agent kinds. Folders whose names start with . are skipped, other files inside a profile folder (a README, say) are left alone, and any other file directly in profiles/ is refused. Profile names use letters, digits, ., _ and -.
Profile inheritance. base = "<profile name>" makes a profile start from another one, so shared settings live in one place and a profile keeps only what differs. A base may have a base of its own (a → b → c), but a profile has one base; a profile that only serves as a base is an ordinary profile. Over the chain, from the root to the profile:
| Field | Rule |
|---|---|
kind |
inherited when left out; a different value than the base's is refused (profile `a`: kind `codex` differs from its base `b` (`claude`)), since args, model and effort mean different things per kind |
model, effort |
replaced when the profile sets them |
args |
the whole list is replaced; lists are not joined |
env |
the whole table is replaced; keys are not merged, and the routing-agent-only rule applies to the result |
description |
never inherited, since the routing agent tells candidates apart by it |
instructions.md |
joined, from the root to the profile (see Profile instructions) |
A profile cannot unset a value its base sets. A loop (profile `a`: its base chain loops: a → b → a) or a base that is not a profile (profile `a`: base `x` is not a profile) is refused when the config loads.
# profiles/claude-base/config.toml: shared by several profiles
kind = "claude"
args = ["--permission-mode", "auto"]# profiles/standard/config.toml
base = "claude-base"
model = "sonnet"
effort = "high"
description = "Scoped features and fixes"A profile becomes agent CLI flags: claude gets --model and --effort, codex gets -m and -c model_reasoning_effort=..., and any other kind gets --model (put the effort in the model ID). args are passed unchanged; this is where permission and sandbox flags belong.
Coordinators on other kinds. A coordinator reads the run folder's AGENTS.md, which Claude Code (through the CLAUDE.md link), Codex, Cursor Agent and OpenCode all read. Cursor Agent and OpenCode ran as coordinators in a Herdr pane, before the subagent switches below were added. Codex 0.160.0 completed an isolated documentation workflow through merge, deployment verification, postmortem and supervisor persistence, with operator assistance for sandbox setup, commit creation and review; see the live evidence and limits. Use the explicit startup smoke test to check a profile without sending a prompt:
# profiles/coordinator-codex/config.toml
kind = "codex"
model = "gpt-6-sol"
effort = "high"
args = ["-c", "agents.enabled=false"] # no subagents of its own (see below)
description = "Coordinator on Codex"# profiles/coordinator-cursor/config.toml
kind = "cursor"
model = "grok-4.7-medium" # Cursor has no effort flag: the effort is part of the model ID
args = [
"--trust", # otherwise Cursor asks to trust every new run folder
"--allowed-tools", "shell_tool_call,read_tool_call,ls_tool_call,glob_tool_call,grep_tool_call",
]
description = "Coordinator on Cursor Agent"# profiles/coordinator-opencode/config.toml
kind = "opencode"
model = "openai/gpt-6-luna" # provider/model, as `opencode models` lists it
description = "Coordinator on OpenCode"- Codex 0.156.1 asked whether you trust each new run folder, and neither a flag nor a
-c projects...override skipped that dialog. The 0.160.0 startup check with-s read-onlyreached interactive readiness without a trust dialog. Other permission settings may still require an answer in Herdr; the answer is saved in your Codex config. - Cursor Agent asks before it runs a command that is not in its allow-list. The plugin writes
.cursor/cli.jsoninto each run folder, allowing the plugin's binary by the path the sheet gives the coordinator, as it writes.claude/settings.local.jsonfor Claude Code. - OpenCode starts as
opencode mini: its full interface takes no model flag, andminitakes--modeland the--sessiona resume adds. OpenCode lets agents run commands unless your OpenCode config says otherwise.
No subagents in a coordinator. Workers are started by the plugin as Herdr panes. A coordinator does not use its kind's own subagents, and it waits by ending its turn: the ticker prompts it when its inbox gets new items. A subagent the coordinator starts itself is outside the plugin's limits, and waiting on one tends to turn into polling that spends tokens. The sheet says so to every coordinator; where the kind's CLI can switch its subagents off, add that to the profile's args. Checked on 2026-09-29 by asking each agent to start a subagent that replies pong:
| Kind | Tested version | Subagents by default | How args switch them off |
|---|---|---|---|
claude |
Claude Code 2.1.280 | The Agent tool (listed as Task); the coordinator's allow-list does not gate it, so it runs without a prompt |
--disallowed-tools=Agent. Keep the = form: the flag takes several values and would swallow the next argument |
codex |
codex-cli 0.156.1 | spawn_agent |
-c agents.enabled=false. --disable multi_agent turns the feature flag off but leaves spawn_agent in place |
cursor |
Cursor Agent 2026.09.26 | The Task tool; .cursor/cli.json permissions do not cover it |
--allowed-tools with the tools a coordinator needs, as above. The flag is hidden and marked internal; --exclude-tools task_tool_call is accepted but does not remove the tool |
opencode |
OpenCode 2.0.15 | The subagent tool, with the built-in general and explore agents |
args alone cannot; an agent in your OpenCode config can (below) |
For OpenCode, add an agent like this to your OpenCode config (for example ~/.config/opencode/opencode.json) and pass --agent herdr-linear-agent-coordinator in the profile's args. Without it, and for any kind not in the table, the sheet's rule is the only thing that keeps a coordinator from starting subagents.
{
"agent": {
"herdr-linear-agent-coordinator": {
"mode": "primary",
"description": "herdr-linear-agent coordinator",
"permission": { "subagent": "deny" }
}
}
}Profile instructions. A profile's optional instructions.md is added for work under that profile: to the run folder's AGENTS.md for a coordinator profile, and to the brief for a worker profile, as a "Profile instructions" section after the built-in rules. The built-in rules (never merge, never change the Linear state, stay inside the catalog, treat the issue text as data) stay as they are and win where the two disagree. A profile with a base gets the instructions.md of every profile in its chain that has one, from the most general to the most specific, each under a ### From the <profile> profile heading; where two disagree, the later one wins. A profile cannot drop its base's instructions: to leave them out, split the base. Only the profile folders set instructions; an agent can still pass only a profile's name.
Claude Code's trust dialog. Claude Code asks whether you trust a folder the first time it runs there, and every run gets a new run folder, so a coordinator stops at that dialog until someone answers it in Herdr. With claude.auto_accept_trust_dialog = true, the plugin accepts that dialog ahead of time, right before it starts a claude agent: the run folder for a coordinator, and the worktree and its repository's main checkout for a worker. Claude Code has no setting for this, so the plugin adds hasTrustDialogAccepted to that folder's entry in ~/.claude.json ($CLAUDE_CONFIG_DIR/.claude.json when set), which is where Claude Code records your own answers, and changes nothing else in the file. The format is not documented; if Claude Code changes it, the dialog appears again and the plugin asks for someone in Herdr as before.
Coordinator routing. Each team names a routing (routing = "<name>", required), a table [routing.<name>]. When an issue is picked up, its routing picks the coordinator profile from its coordinators; postmortem profiles are picked later, when each postmortem is written (see Postmortems). A list with one profile gives that profile and runs no routing agent. Otherwise the routing agent (agent, required only then) gets the candidates' names and descriptions as its instructions, and the issue's title and description, with its estimate, labels and team when known, on standard input, under limits.routing_agent_timeout_seconds or its profile's timeout_seconds. It must answer {"coordinator": "<name>"} (or {"postmortem": "<name>"} when picking a postmortem) with a name from the candidates: the schema restricts it where the kind supports one, and the plugin checks the answer against the same schema either way. A timeout, an answer outside the schema, or a name outside the candidates gives the first profile of the list. The Linear thought names the profile and how it was chosen. agent must be a profile of a kind listed below; any other kind is refused when the config loads.
Whatever the issue says, the routing agent can only pick one of the configured candidates, so its context is cut for cost, speed and a steadier choice, not for safety. It runs in a fresh empty folder that is removed afterwards, with your real HOME, config dirs and environment (where the CLIs keep their login), and never with the profile's args. Each kind cuts what its CLI lets it cut; the rest remains, as the table says. Measurements are in docs/verification.md.
| Kind | Tested version | Item | How it is cut | Remains, and why |
|---|---|---|---|---|
claude |
Claude Code 2.1.280 | Default system prompt | --system-prompt with a short fixed instruction |
An environment block (working directory, platform, OS version), the model's identity, the account's email and the date, which Claude Code always adds |
| Tools | --tools "" (only the structured-output tool is left) |
|||
| MCP | --strict-mcp-config --mcp-config '{"mcpServers":{}}', ENABLE_CLAUDEAI_MCP_SERVERS=false, --safe-mode |
|||
| Skills, slash commands | --disable-slash-commands, --safe-mode |
|||
| Plugins | --safe-mode |
|||
| Hooks | --safe-mode, --restricted |
|||
| User and project settings | --setting-sources "", --restricted |
Managed (organization) settings still apply | ||
| Instruction files | CLAUDE_CODE_DISABLE_CLAUDE_MDS=1, --safe-mode (Claude Code reads AGENTS.md only through a built-in plugin that --safe-mode turns off) |
The user-level ~/.claude/CLAUDE.md is covered by the same flags but was not tested, to leave your config untouched |
||
| Auto-memory | CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, --safe-mode |
|||
| Session history and persistence | --no-session-persistence, CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 |
|||
codex |
codex-cli 0.156.1 | Default system prompt | -c model_instructions_file= with a short fixed file; include_permissions_instructions, include_apps_instructions, include_environment_context set to false |
A startup request Codex always sends |
| Tools | --disable for shell, exec, browser, image, multi-agent and similar features; -s read-only; web_search="disabled"; agents.enabled=false |
Two code-mode tool definitions (about 1,350 tokens) that the model's catalog requires; calling them fails because code mode is off | ||
| MCP | --ignore-user-config (project config is not loaded either) |
|||
| Skills | skills.include_instructions=false, skills.bundled.enabled=false |
|||
| Plugins | --ignore-user-config, --disable plugins |
|||
| Hooks | --disable hooks |
|||
| User and project settings | --ignore-user-config, --ignore-rules |
|||
| Instruction files | project_doc_max_bytes=0 for project AGENTS.md |
~/.codex/AGENTS.md (and AGENTS.override.md), which Codex always loads from CODEX_HOME |
||
| Auto-memory | --disable memories |
|||
| Session history and persistence | --ephemeral, history.persistence="none" |
|||
cursor |
Cursor Agent 2026.09.26 | Default system prompt | none: Cursor offers no system prompt option; the fixed instruction is the call folder's AGENTS.md, which Cursor applies as a rule |
Cursor's own system prompt, the user rules synced from your Cursor account, and any AGENTS.md or .cursor/rules in a folder above the call folder (normally none: it is under the system temp dir) |
| Tools | --allowed-tools "" (an internal flag of this version: every tool call answers "Tool not available"), and the call folder's .cursor/cli.json denies shell, read, write, web and MCP |
|||
| MCP | denied as above, and project MCP servers need an approval the call never gives | Descriptions of MCP servers that Cursor plugins bring (they cannot be called) | ||
| Skills | Skills under your home folder (~/.claude/skills, ~/.agents/skills, Cursor's own) |
|||
| Plugins, hooks | Anything under ~/.cursor (mcp.json, hooks.json, rules) and in a folder above the call folder |
|||
| User and project settings | --trust (otherwise Cursor stops at its trust prompt); the call folder's own .cursor/cli.json |
Your ~/.cursor/cli-config.json |
||
| Auto-memory | none in Cursor Agent's print mode | |||
| Session history and persistence | Each call's chat in ~/.cursor/chats and a ~/.cursor/projects folder, unless the profile's env sets CURSOR_CONFIG_DIR and CURSOR_DATA_DIR (below) |
|||
opencode |
OpenCode 2.0.15 | Default system prompt | an agent of its own in OPENCODE_CONFIG_CONTENT whose prompt is the fixed instruction |
An environment block (session id, working directory, platform) and the date |
| Tools | that agent's permission denies everything ({"*": "deny"}) |
|||
| MCP, plugins, skills, agents, settings (project) | OPENCODE_DISABLE_PROJECT_CONFIG=1; --standalone runs a private server, since the shared background service ignores the call's environment |
The global config in ~/.config/opencode (its MCP servers, plugins and agents) still loads, unless the profile's env sets OPENCODE_CONFIG_DIR (below) |
||
| Hooks | OpenCode's hooks are plugins: as above | Hooks of global plugins | ||
| Instruction files | the project layer, as above (OpenCode does not read CLAUDE.md) |
~/.config/opencode/AGENTS.md, unless OPENCODE_CONFIG_DIR is set |
||
| Auto-memory | none in OpenCode | |||
| Session history and persistence | "snapshot": false, "share": "disabled", and the plugin deletes the session afterwards (opencode session delete) |
A stored copy of the environment block, a project row and an empty shell/ folder per call in ~/.local/share/opencode, which the CLI cannot remove |
Routing agent environment. The routing agent's profile may set env, variables added to its call (the recipe's own variables win over them). Only that profile may set it: Herdr starts coordinators and workers and cannot pass them variables, so a profile with env that is also a coordinator or worker candidate is refused. Its main use is to give a kind an empty config folder of its own where its login does not live there, which cuts what the table above leaves:
# profiles/router-opencode/config.toml
kind = "opencode"
model = "openai/gpt-6-luna#low"
env = { OPENCODE_CONFIG_DIR = "/Users/me/.local/state/herdr-linear-agent/router/opencode" } # an empty folder you createOPENCODE_CONFIG_DIRreplaces the global config folder, so the globalopencode.json, its MCP servers, plugins and agents, and~/.config/opencode/AGENTS.mdno longer load. The login lives in OpenCode's database and keeps working.CURSOR_CONFIG_DIRandCURSOR_DATA_DIR(the second is undocumented) keep Cursor Agent's chats, projects and settings out of~/.cursor; they do not cut prompt tokens, and your default model is not read, so setmodel. The login lives in the Keychain and keeps working. Cursor writes each call's chat into that folder, so empty it now and then.- Do not point Claude Code's
CLAUDE_CONFIG_DIRor Codex'sCODEX_HOMEthere: their login lives in those folders.
Cursor Agent and OpenCode have no schema option, so the plugin checks the JSON they print itself (Cursor's result, OpenCode's last text event). Put Cursor's effort in the model ID: model = "grok-4.7-low". Put the effort in the model as a variant: model = "openai/gpt-6-luna#low".
Other kinds are not registered.
- The ticker (a background process the startup hook starts) polls each workspace every 5 seconds (
intake_interval_seconds) for issues delegated to its app user in the configured teams that are neither completed nor canceled. - It checks who delegated each new issue. An issue that someone outside the team's
allowed_delegator_idsdelegated gets no run: the ticker answers its session once, logs the delegator's name and ID and shows a Herdr notification. - For a new issue it creates a run folder, creates an Agent Session, posts a first thought, moves the issue to the team's configured started state, picks the coordinator profile, and opens a Herdr workspace with the coordinator in the run folder.
- The coordinator runs
herdr-linear-agent contextevery turn, publishes a plan, asks questions, and starts workers withherdr-linear-agent worker start. If a worker is still working, it says what it is waiting for and ends its turn. Once all workers have reported,herdr-linear-agent waitcan end a completed turn while keeping the issue active;finishmoves work ready for review. The command durably queues the request. The ticker creates the Git worktree, prepares it, and opens its tab on a branchherdr-linear-agent/<workspace>/<issue-key>/<id>-<title>. - Workers report what they are doing (
herdr-linear-agent report --activity) and write a report (PR: <url>,## Report,## Next). The ticker shows each new activity in the session, posts a worker that waits on someone, its report and its pull requests, copies the report into the run folder, and tells the coordinator through its inbox. After 10 quiet minutes it posts what each worker is doing and for how long. - Replies in the session from allowed users are appended to
conversation.mdand the coordinator is prompted with one fixed line. Changes to the issue's Linear workflow state create inbox items for the coordinator. A stop signal interrupts the run's agents. herdr-linear-agent finishposts the summary and moves the issue to its team's review state once every worker has reported. A confirmed worker merge moves the issue to deployment and resumes the coordinator. After deployment work, a laterfinishmoves the issue to its postmortem state. A successful workflow postmortem advances it to the completed state.- When the issue is completed or canceled, the ticker records the close request and ends the session with a short response. It closes managed panes, preserves reports and removes clean worker checkouts without forcing removal. Branches remain. Interrupted cleanup resumes after a ticker restart; refused removals retain their checkout and reason.
If Linear removes the delegate or archives/dismisses the run's session, the ticker pauses the run, interrupts its agents and keeps its worktrees and history. It posts an ordinary issue comment with an incident-specific code. To authorize restoration, add an issue comment containing exactly resume <code>; stop <code> declines that request and leaves the run paused. The author must be a human in both the team's allowed_user_ids and allowed_delegator_ids (which defaults to allowed_user_ids). Restoring the delegate in the UI does not bypass a pending comment request.
After approval, the Linear task restores delegation and opens a new Linear session rooted on the consent request, verifies both, then reconnects the existing run. The old session remains as history; the plugin does not unarchive or un-dismiss it through internal APIs. A later removal requires a new code, and another agent's delegation is not replaced. Detached runs continue to monitor completion/cancellation, so cleanup and the final postmortem do not depend on approval or the old session remaining accessible.
When a pane needs a person (a permission or trust dialog), the ticker says so in the session with the pane to go to, and shows a Herdr notification.
If an agent process exits while its Herdr pane remains open, the ticker confirms the pane is still empty for 30 seconds, then can resume that agent in the same pane and native session. It waits 15, 30 and 60 seconds before up to three automatic resume starts (in addition to the original launch); the attempt budget survives successful resumes and ticker restarts. A pane occupied by another agent is left alone. A missing native session or exhausted budget marks recovery stale; the coordinator receives an explicit resume action, and a worker's coordinator inbox gets a manual restart item.
With merge_on_authorization = true, the coordinator requests one-time authorization for the worker's recorded PR; an allowed user's exact merge <token> reply is required before worker merge can make one attempt. With merge_decision configured, worker request-merge instead queues an asynchronous assessment. context shows its durable status and score even after the inbox notice is handled. A score at or below the configured threshold is eligible, but it is not approval: the coordinator explicitly invokes worker merge, which reacquires and validates GitHub facts, issue/report contents and the complete diff, then consumes the receipt and merges only with the assessed head SHA. Open, draft, mergeability, review, check, repository identity or evidence failures block assessment or merge. A successful or ambiguous merge is always read back; unknown results are never retried. A fresh request can replace a terminal assessment, so the coordinator should have a concrete reason to reassess rather than rerolling for a lower score.
For this workflow, set the Linear team's Workflows & automations → On PR merge, move to… to the configured deployment state, or to No action. If the GitHub integration moves the issue directly to Done, the plugin closes the run and writes its final postmortem. Deployment work and the workflow postmortem do not run. Queued workflow transitions leave completed and canceled issues alone.
| Action | What it does |
|---|---|
| herdr-linear-agent: log in to Linear | Authorizes each workspace's app without a token in the browser (all of them again when each has one) and stores the tokens in the Keychain or Secret Service |
| herdr-linear-agent: status | Lists the runs, their coordinators and workers |
| herdr-linear-agent: open this run's issue | Opens the issue of the focused pane's run in the browser |
| herdr-linear-agent: focus the run of a Linear issue | Ctrl-click a Linear issue link to focus its run |
| herdr-linear-agent: stop taking new issues | Pauses intake; running runs continue |
| herdr-linear-agent: take new issues again | Resumes intake |
| herdr-linear-agent: check setup | Checks Herdr, the config, the agent CLIs, each workspace's login and the ticker |
| herdr-linear-agent: reload the config | Checks the config and makes the running ticker start its tasks again with it (starts the ticker when none runs) |
| herdr-linear-agent: browse past runs | Opens the past-runs browser over the focused pane (see Past runs) |
A run's panes close when it ends. To look back at how a run went, run herdr-linear-agent: browse past runs, or herdr-linear-agent history in any terminal:
- The list. It shows every run of every workspace, the most recently updated first, with its key, the issue title, its state, when it was last updated, and its pull requests. Type to filter it, fuzzy, as in fzf. The right side previews the selected run:
issue.md,conversation.md, each worker's report and the pull requests. - Transcripts. Enter lists the run's coordinator and workers, one line per transcript each left, and shows the selected one: what the agent and the person said, each tool call, and the first lines of each result. It is read-only. Claude Code, Codex, Cursor and OpenCode transcripts are read. The line and the view say whether the text comes from the run folder's copy or from the agent's own files; for a transcript that is gone, the run folder's records are shown instead.
- Resume.
ropens a Herdr workspace in the agent's folder and resumes the selected session there, for kinds that can resume one (Claude Code, Codex, OpenCode, Cursor). It is offered only while the agent is not running and its folder is still there.
Keys: ↑↓ (or Ctrl-p/Ctrl-n) move, PgUp/PgDn (or Ctrl-u/Ctrl-d) scroll the right side, Esc goes back, and Esc on the list, q on the transcripts or Ctrl-c quits.
The screens use only the terminal's 16 ANSI colors, so they follow the Herdr theme, dark or light, and change with it. In a transcript the person's messages are green, the agent's blue, tool calls yellow, results gray and errors red.
Sessions and kept transcripts. No Herdr agent integration is needed. The plugin records each agent's session itself: it starts Claude Code with a session id it picks (--session-id), and finds the session Codex, OpenCode or Cursor began after the start in the agent's folder. So a coordinator whose pane was closed, or a closed run delegated again, resumes its previous session. When a run closes or is detached, and before worker restart, the plugin copies each agent's transcript into the run folder (.state/transcripts/<agent>/<session>.jsonl, OpenCode's export as .json): the agent CLIs may delete their own files later, and the copy stays. It reads Claude Code's ~/.claude/projects (or $CLAUDE_CONFIG_DIR/projects), Codex's ~/.codex/sessions (or $CODEX_HOME/sessions), Cursor's ~/.cursor/projects, and opencode session list and opencode session export, and writes to none of them.
| Place | Contents |
|---|---|
$XDG_CONFIG_HOME/herdr-linear-agent/config.toml |
Your config |
$XDG_STATE_HOME/herdr-linear-agent/runs/<workspace>/<ISSUE-KEY>/ |
A run: issue.md, conversation.md, worker records and reports, the coordinator's inbox, the outbox |
$XDG_STATE_HOME/herdr-linear-agent/worktrees/<workspace>/<ISSUE-KEY>/ |
Worker Git checkouts |
$XDG_STATE_HOME/herdr-linear-agent/ticker.log |
The ticker's log |
$XDG_STATE_HOME/herdr-linear-agent/supervisor/ |
Supervisor lifecycle, reports, decisions, checkpoints, generated catalog and knowledge; memory/index.sqlite3 is a rebuildable search index |
<worktree>/.herdr-linear-agent/<workspace>-<ISSUE-KEY>-<id>/ |
A worker's brief and report (see below) |
$XDG_STATE_HOME defaults to ~/.local/state on Linux and macOS alike.
A worker's brief and report live inside its worktree because the worker runs there: sandboxed agents, such as Codex with -s workspace-write, can write freely only inside their working directory. To keep that folder out of commits, the ticker adds .herdr-linear-agent/ to the repository's info/exclude. Every worktree of a repository shares that file with the main checkout, so the rule covers all of them, and it never changes a tracked file such as .gitignore. git add -A, git add . and git commit -a all leave the folder out; only a forced git add -f would stage it. The ticker copies each report into the run folder (workers/<id>.md), so it outlives the worktree.
The optional [repositories.<name>.worktree] table configures setup in the plugin config. An absent table runs no hooks. copy accepts regular files and symlink accepts files or directories. Entries are relative paths inside the main checkout. Missing sources are skipped, existing destinations are preserved, and overlapping destinations are rejected. Copies become visible only after the complete file has been written.
Setup runs before the worker agent starts. post_create commands run in the worker checkout, in order. post_remove commands run in the main checkout after checkout removal. Each command runs through sh -c with the ticker environment plus HLA_RUN_KEY, HLA_WORKER_ID, HLA_REPO, HLA_REPO_PATH, HLA_WORKTREE, HLA_BRANCH, and HLA_BASE. These values contain no Linear credential. Hook output is bounded and recorded in the worker's setup log in the run folder.
An operation retains its accepted hook configuration and progress across reloads. A crash after a command takes effect but before its completion is saved can repeat the command. Make hooks idempotent. A worker ID such as w1 repeats across runs; use a run-specific identifier for external resources. For Docker Compose, hash HLA_BRANCH into a valid project name and use the same name for creation and teardown.
Completion or cancellation requests cleanup. Detaching, losing a coordinator, or manually closing the workspace does not request checkout removal by this plugin. Git refuses removal of checkouts with tracked changes or untracked files. Git-ignored files, including copied configuration and worker briefs, can be deleted with a clean checkout. Reports are copied to the run folder before removal. A refused cleanup retains its phase and error in workers/<id>.toml and in context. After resolving the cause, run herdr-linear-agent worker retry-cleanup acme/DATA-1 w1 while the run is closed. The retry resumes the failed phase and keeps completed hook cursors.
Other plugins retain their own policies. In particular, herdr-worktree-cleanup can remove registered checkouts when their owner space closes. Legacy Herdr worktrees already have that registration; ordinary Git hooks can also register new checkouts. The plugin does not assume that using the Git CLI prevents registration. Check external cleanup configuration before relying on checkout retention.
- Anyone who can delegate an issue in a configured team could otherwise start agents on your machine, so the ticker takes only issues that a person in the team's
allowed_delegator_ids(default:allowed_user_ids) delegated. The delegator is whoever made the latest delegation to the app, as the issue history records it; for an issue delegated as it was created, which leaves no history entry, it is the creator of the Agent Session Linear opened then. Through Linear's Slack integration, that is the person who asked in Slack. The assignee does not count, since anyone can change it before delegating. An issue that automation or another agent delegated, or whose delegator Linear does not tell, is not taken either. A closed run starts again when an allowed person delegates its reopened issue. A detached run with a pending consent request resumes only after comment approval by a human allowed both to reply and to delegate, followed by verified restoration. The session of a declined delegation gets one response,This agent does not take issues delegated by this user. Ask someone allowed to delegate it., which ends it. - Agents run as your user. The allow-list the plugin writes for Claude Code and the rules in the coordinator sheet are guidance, not a sandbox: an agent can run any command your shell can.
claude.auto_accept_trust_dialogmakes the plugin accept Claude Code's trust dialog for you in its run folders and in worktrees of your catalog repositories. Turn it on only for repositories you already trust.- The plugin has no subcommand that prints the token. The macOS Keychain may ask for confirmation when a rebuilt binary reads it; a locked Secret Service collection asks to be unlocked.
- The issue text, reports and comments are treated as data. Only replies from the
allowed_user_idsof the issue's team reach the coordinator as instructions; others are recorded in the run's.state/ignored-prompts.mdand told in the log and a notification.
The Linear integration is tested against an in-memory fake of the Linear API. The checks that need a real Linear workspace and a person are listed in docs/verification.md.
mise install --locked
mise run lint
mise run test
mise run buildGitHub Actions dependencies are recorded in .github/workflows/actions.lock.
The workflows also keep their full commit SHA pins. mise installs the pinned
gh-actions-lock binary; no GitHub CLI extension installation is needed.
After adding, removing, or changing workflow or composite action dependencies,
authenticate with gh auth login (or set GH_TOKEN) and run:
mise run actions-lock
mise run actions-lock-verifyCommit the generated lockfile together with the workflow changes. Never edit it
by hand. mise run lint checks coverage offline; pull request and push CI verify
the pins against GitHub. Regenerate before pushing: a workflow with missing
lockfile entries can be rejected before its autofix job starts. Renovate updates
the lockfile in its GitHub Actions update pull requests.
Review changed commits and investigate changes to owner_id or repo_id.
The generator preserves existing pins. If an upstream reference moves, review
the upstream history before using mise exec -- gh-actions-lock --relock;
do not automatically accept moved or unreachable commits.
External reusable workflows use the lockfile in their defining repository. See Automation setup for the current coverage and the repository's Require lockfile policy.
If you rebuild and run the plugin against your Linear workspaces, use an Apple Development signing identity to avoid repeated Keychain authorization after rebuilds. Follow the development signing guide to create the certificate, check its private key and intermediate certificate, and verify access across rebuilds. Users installing published releases do not need Xcode, a signing certificate, or this task; ordinary Keychain access prompts still apply. Running lint or tests alone does not need a signing identity either.
export HLA_SIGN_IDENTITY="Apple Development: YOUR_ACCOUNT (IDENTITY_SUFFIX)"
mise run build-developmentThis builds and signs target/release/herdr-linear-agent, the path used by the
plugin manifest. Use it after each source change in your development checkout.
Development mode enables contributor tools for isolated integration checks,
agent startup checks and Herdr event inspection. It defaults to false and
permits the development commands below. Enabling it alone starts no processes;
ordinary commands keep their usual configuration, state and lifecycle behavior.
The supervisor is enabled separately through [supervisor].
Choose any one of these opt-ins:
# At the top of config.toml, before any [table]:
development_mode = trueherdr-linear-agent --development-mode development harness --help
HERDR_LINEAR_AGENT_DEVELOPMENT_MODE=true herdr-linear-agent development harness --helpThe setting is read from $XDG_CONFIG_HOME/herdr-linear-agent/config.toml
(default ~/.config/herdr-linear-agent/config.toml) for each development command.
Any one opt-in enables the commands; the environment accepts literal true or
false, and false does not override a config or CLI opt-in. The flag or
environment variable lets you initialize a harness before configuring
workspaces or profiles. Use herdr-linear-agent development --help for help;
the group is hidden from the top-level command list.
Command after herdr-linear-agent development |
Effect |
|---|---|
harness init ROOT --session NAME |
Creates a new private root, isolated plugin/Herdr config and state paths, a session marker and config with development mode enabled. Starts no processes. The parent must exist and the root must be outside ordinary plugin paths. |
harness herdr ROOT [ARGS...] |
Starts or attaches Herdr using the dedicated non-default session. HERDR_BIN_PATH can select the executable. |
harness plugin ROOT ARGS... |
Runs the same plugin executable with isolated XDG paths and the root as its working directory. |
harness exec ROOT /absolute/command [ARGS...] |
Runs another tool in that same environment. |
harness fixture ROOT --repository URL --workspace NAME --team KEY --client-id ID --allowed-user ID [--model MODEL] |
Clones the same explicit scratch repository into api and web, creates ignored fixture files, writes read-only profiles and a fixture-manifest.json. |
harness verify ROOT workspace/TEAM-N --terminal done|canceled [--timeout-seconds 300] [--pause-after-workers] |
Runs the bounded real Linear, Herdr and saved-state issue 140 scenario. It stops the isolated ticker at completion and retains per-checkpoint evidence. For done, the optional pause keeps worker tabs and the ticker running for visual inspection until explicit resume or the checkpoint deadline. |
harness resume ROOT workspace/TEAM-N |
Resumes a verifier waiting at the worker inspection checkpoint. Rejects missing, expired or completed inspection checkpoints. |
harness ordinary-suspend ROOT --plugin PATH / harness ordinary-restore ROOT |
Records and stops the exact ordinary ticker binary before a live check, then verifies protected ordinary files and restarts that binary. |
harness hook ROOT STEP |
Internal fixture hook used by worker setup/removal. STEP is first, wait or remove. |
harness stop ROOT |
Attempts to stop the isolated ticker, stop and delete its session, and retains all files as evidence. Attempts all cleanup steps even if one fails. |
agent-smoke --session NAME --kind KIND |
Starts a real agent in a temporary coordinator workspace in an already running non-default test session, checks interactive readiness without sending a prompt, then removes the workspace. Accepts codex, claude, cursor and opencode. |
agent-smoke --session NAME --profile PROFILE [--config-dir DIR] |
Checks a configured profile with inherited instructions and arguments. Defaults to the ordinary config directory; refuses arguments not audited for prompt-free startup. Both smoke forms accept --timeout-seconds 5..300 (default 30). |
herdr-watch [--socket PATH] |
Follows the given socket or configured Herdr session and prints connection, wake count and agent snapshots as JSON until interrupted. |
For an isolated check from a source checkout:
export HERDR_LINEAR_AGENT_DEVELOPMENT_MODE=true
target/debug/herdr-linear-agent development harness init /tmp/hla-development --session hla-development
# Configure scratch workspaces, repositories and profiles in
# /tmp/hla-development/config/herdr-linear-agent before starting the ticker.
target/debug/herdr-linear-agent development harness herdr /tmp/hla-development
# In another terminal with the same opt-in:
target/debug/herdr-linear-agent development harness plugin /tmp/hla-development action doctor
target/debug/herdr-linear-agent development harness plugin /tmp/hla-development ticker start
# When finished:
target/debug/herdr-linear-agent development harness stop /tmp/hla-developmentUse the signed target/release/herdr-linear-agent from build-development for
real macOS credential access. Harness commands isolate config, state and Herdr
worktrees and clear inherited plugin/Herdr location overrides. Agent panes
inherit those XDG paths. HOME, native agent data, OS credentials and GitHub
authentication remain shared; profile permissions control the agent sandbox.
Use scratch teams and repositories, and retain the dedicated session settings.
See the integration-harness skill
for general live-check safety. The issue 140 verification procedure
records the exact prerequisites, commands, operator actions, evidence and limits.
herdr-linear-agent is licensed under the MIT License. See LICENSE.