| name | lira |
| description | Operate the lira CLI, a local agent-native task system where local tickets are local-first issue records decomposed into acceptance criteria plus atomic tasks. Use when the user wants to break down Jira/GitHub/local work into agent tickets; create, claim, work, comment on, sync, query, or close local tickets; manage acceptance criteria and atomic tasks; resolve GitHub sync conflicts; or expose local candidates and normalized issue projections for a Symphony-style runner through ~/.lira/. |
lira - Local Jira for Agents
lira is a Rust CLI that gives agents a durable, inspectable, local tracker for
decomposing work into agent-executable tickets. A local lira ticket can be a
child of a canonical Jira ticket, a peer of a GitHub Issue, or a purely local
agent ticket. lira holds the agent's local breakdown into acceptance criteria
and atomic tasks the agent can execute, comment on, and complete.
Tickets live as YAML under ~/.lira/, can optionally bind to a peer GitHub
Issue for outside visibility, and never write back to Jira in v1. Jira is
read-only context. lira can also expose normalized issue projections and
candidate lists for a Symphony-style orchestrator, but the runner/daemon remains
outside lira.
Mental Model
The expected planning hierarchy usually has three layers:
- Jira ticket, such as
VAN-1234: the canonical, team-visible work item.
Read-only from lira's perspective. Holds the high-level "what" and "why."
- lira ticket, such as
ORION-48: the local agent's decomposition of one Jira
ticket into a single agent-executable scope. Has acceptance criteria and
atomic tasks.
- lira tasks, such as
T1, T2: atomic to-dos inside one lira ticket. Each is
small enough to finish in a single focused step.
Normal Jira-to-lira flow:
- Fetch current Jira context with
lira jira fetch VAN-1234 --json.
- Create a local working ticket with
lira new ... --parent-jira VAN-1234,
including at least one --acceptance-criterion and one --task.
- Claim, move, drive tasks, comment, and accumulate history.
- Optionally mirror the ticket to a GitHub Issue with
lira gh create.
- Close the lira ticket when its local scope is done. The Jira parent remains
untouched; humans update Jira from lira history or the synced GitHub Issue.
One Jira parent can have multiple lira children if the work splits across
agents, sprints, or scopes. Do not cram a multi-scope Jira ticket into one lira
ticket.
A lira ticket without a Jira parent is allowed but should be the exception, such
as agent-internal infrastructure work with no team-level counterpart. If the
user gives work without a Jira reference, ask whether one exists before creating
a parentless ticket.
For Symphony-style orchestration, treat lira as the local issue tracker/control
plane. A runner may poll candidates, claim one ticket, launch Codex elsewhere,
and write progress back through lira comments, history, task updates, and status
moves. lira does not launch Codex app-server, own source-code workspaces, manage
retry timers, or perform stall detection.
Preflight
Before doing real work, confirm lira is installed and the workspace exists:
lira --version
ls ~/.lira/ 2>/dev/null || echo "not initialized"
lira project list --json
If ~/.lira/ does not exist, ask the user before running lira init. That
directory is the canonical store for all their tickets across every project, not
scoped to the current repo.
Core Rules
These are operational requirements, not style preferences:
- Always pass
--json. Every command supports stable, versioned JSON output
with schema_version, ok, and either result or error. Parse those
fields. Human-format output can change shape.
- Acceptance criteria are mandatory. A ticket cannot exist without a non-empty
acceptance_criteria list. Creation, GitHub adoption, and import all fail
with E_ACCEPTANCE_CRITERIA_REQUIRED if missing.
- Tasks are mandatory. Every ticket must have at least one entry in
tasks[].
The same enforcement returns E_TASK_REQUIRED.
- Tasks are atomic and minimal. A task has only
id, title, status,
tags, created_on, and last_modified. If a task needs comments,
assignees, descriptions, estimates, or external links, promote it to a child
ticket with lira child add.
- Status lives in the directory. A ticket's location, such as
~/.lira/projects/ORION/tickets/in-progress/ORION-42.yaml, is part of the
source of truth alongside the status field. Use lira mv to change status.
Never edit YAML or move files manually.
- There is no hard delete. Cancel, archive, or release tickets. Removing a
ticket file bypasses history, the index, and any GitHub binding.
- Claim before mutating someone else's work.
lira claim <ID> --agent <name>
fails if another agent owns the ticket. Respect that signal unless the user
explicitly tells you to steal with --force.
- The completion guard is real.
lira mv <ID> done fails with
E_COMPLETION_POLICY unless acceptance criteria exist and all tasks are
terminal (done or cancelled). Drive tasks to terminal first.
- Default to a Jira parent. Parentless tickets are a deliberate exception, not
a default.
- YAML is canonical. SQLite at
~/.lira/index/tickets.sqlite and
~/.lira/gh-cache/ are caches. If they look wrong, run lira reindex; do
not hand-edit.
- Orchestration reads are pure.
lira candidates, lira issue show, and
lira issue current must not mutate tickets or create noisy logs.
- lira is the tracker, not the runner. Do not expect it to start Codex,
create per-issue workspaces, retry failed runs, or kill stalled sessions.
Create From Jira
Start by pulling the Jira parent so you have its title, description, and context
to decompose against:
lira jira fetch VAN-1234 --json
Create the local subticket with --parent-jira and at least one
--acceptance-criterion and one --task. Both flags are repeatable:
lira new "Implement CUPED variance reduction in experiment-analyst" \
--project ORION \
--parent-jira VAN-1234 \
--type task \
--priority high \
--assignee athena-analyst \
--acceptance-criterion "Variance reduced by >=30% on holdout dataset." \
--acceptance-criterion "Report includes both baseline and adjusted variance." \
--task "Add SQL covariate extraction." \
--task "Implement CUPED adjustment calculation." \
--task "Add tests for null covariates and treatment groups." \
--json
The response returns the new ticket ID, such as ORION-48, and default status,
usually backlog. Capture the ID for follow-up calls.
Decompose Work
At creation time, translate the Jira parent's prose into two distinct artifacts:
- Acceptance criteria: observable, testable outcomes a reviewer could check
without watching the agent work. Pull from Jira language when possible.
- Tasks: atomic implementation steps the agent can execute one at a time. These
are the agent's decomposition and usually do not appear in Jira.
A criterion is what done looks like to an outside observer. A task is one move
toward getting there.
If the Jira parent is too broad for one lira ticket, create multiple sibling lira
tickets sharing the same --parent-jira, each with its own focused scope,
criteria, and tasks.
If the user gives only a Jira key and rough direction, draft criteria and tasks
yourself and show them before submitting lira new. Let the user edit if they
want. Do not ask the user to enumerate every task.
Pick Up Work
lira next --project ORION --agent athena-analyst --json
lira claim ORION-48 --agent athena-analyst --json
lira mv ORION-48 in-progress --json
lira active --agent athena-analyst --json
lira next returns the highest-priority unclaimed candidate. If none exist, the
result is empty. Surface that to the user; do not invent work or claim
arbitrarily.
Use lira active --agent <name> --json at the start of a session to recover
state for an agent that already owns work.
Symphony-Style Candidate Reads
For a local orchestrator or scripted runner, prefer the normalized issue surface:
lira candidates --project ORION --json
lira issue show ORION-48 --json
lira issue current --ids ORION-48 ORION-50 --json
lira workflow symphony export --project ORION --json
lira workflow symphony validate ./WORKFLOW.md --project ORION --json
lira candidates returns dispatch-eligible local tickets as normalized issues.
It excludes terminal tickets, claimed tickets, tickets disabled for dispatch,
and todo tickets blocked by non-terminal local blockers. Sort order is
priority, then creation time, then identifier.
After reading candidates, claim before starting work:
lira claim ORION-48 --agent symphony-runner --json
If claim fails with E_CLAIM_HELD, do not start a duplicate run. Re-read
candidates or inspect the current owner with lira active.
Work Tasks
Each task mutation automatically appends a history entry on the parent ticket
and updates timestamps.updated. Do not manage history manually for ordinary
task moves.
lira task list ORION-48 --json
lira task status ORION-48 T1 in-progress --json
lira task status ORION-48 T1 done --json
lira task add ORION-48 "Document new CUPED config knobs." --tag docs --json
lira task tag add ORION-48 T2 statistics --json
lira task cancel ORION-48 T3 --json
Task statuses are todo, in-progress, blocked, done, and cancelled.
When a task cannot be completed as written, do not silently retitle it. Choose
one of:
- Add a tighter sibling with
lira task add.
- Cancel the original with
lira task cancel and add a replacement.
- Promote to a child ticket with
lira child add if the work has outgrown
atomic task shape.
Cancellation is recorded in history; retitling hides the change.
Comments And History
Comments are free-form, Jira-style, human-readable notes attached to the ticket.
Tasks intentionally have no comments.
History is the structured, machine-readable, append-only event stream. lira
writes history automatically for mutations; agents add structured entries with
lira history add for durable analysis notes or decisions.
lira comment ORION-48 "Variance baseline = 0.0234 on holdout slice." --json
lira history add ORION-48 \
--action analysis_note \
--message "CUPED assumptions validated against pre-treatment period." \
--actor athena-analyst \
--json
For long bodies, pipe via stdin:
lira comment ORION-48 --stdin --json <<'EOF'
Detailed multi-line note...
With supporting context.
EOF
If a comment should also be pushed to the bound GitHub Issue, mark it for sync:
lira comment sync ORION-48 local-c7 --github --json
GitHub comment sync is append-only in v1. Edits and deletes are not propagated.
Close Tickets
lira mv ORION-48 in-review --json
lira mv ORION-48 done --json
mv done fails with E_COMPLETION_POLICY if any task is non-terminal or if
acceptance criteria are missing. Fix the underlying state by finishing or
cancelling each task. --force is a human override, not an agent shortcut.
Links And Dependencies
The Jira parent is normally set at creation via --parent-jira. Use link
commands for everything else: lira-to-lira parents, blocking relationships,
related tickets, and child tickets.
lira distinguishes two GitHub relationships:
- GitHub parent (
--parent-github): an upward tracking relationship, usually
pointing at a GitHub tracking issue that aggregates work. Local-only; not
synced.
- GitHub peer binding: the ticket's
github block, a bidirectional 1:1 sync
relationship with a single GitHub Issue.
A ticket can have a Jira parent and a GitHub peer binding to a different GitHub
Issue at the same time.
lira link ORION-48 --jira VAN-1234 --json
lira link ORION-48 --parent-lira ORION-12 --json
lira link ORION-48 --parent-github example-org/repo#100 --json
lira link ORION-48 --blocks ORION-50 --json
lira link ORION-48 --relates-to ORION-30 --json
lira child add ORION-48 ORION-49 --json
For visibility into Jira-parented work:
lira jira sync-parents --json
lira ticket list --parent-jira VAN-1234 --json
GitHub Sync
GitHub Issues are first-class peer sync targets. lira shells out to gh and
never stores raw tokens. If gh is missing or unauthenticated, sync commands
return E_GH_NOT_INSTALLED or E_GH_AUTH, and local-only commands keep
working. Do not block local progress on GitHub availability.
Bind GitHub Issues:
lira gh create ORION-48 --repo weisberg/agent_tools --json
lira gh link ORION-48 weisberg/agent_tools#142 --json
lira gh adopt weisberg/agent_tools#142 \
--project ORION \
--acceptance-criterion "Reproduce and fix the reported regression." \
--task "Reproduce locally from the issue body." \
--json
lira gh import weisberg/agent_tools \
--project ORION \
--state open \
--label bug \
--acceptance-criteria-file ./default-ac.yaml \
--task-template "Triage imported GitHub issue." \
--json
gh adopt and gh import enforce the same mandatory acceptance criteria and
tasks rules. If the GitHub body does not contain parseable ## Acceptance Criteria and ## Tasks sections, provide them via flags.
Push, pull, and sync:
lira gh pull ORION-48 --json
lira gh push ORION-48 --json
lira gh sync ORION-48 --json
lira gh sync --all --project ORION --json
lira gh status ORION-48 --json
sync_state values are synced, local-ahead, remote-ahead, conflict,
unbound, and disabled. Always check sync_state before assuming either side
is authoritative.
Conflict Handling
lira gh sync detects conflicts via three-way reconciliation against
last_synced, remote_etag or remote_body_hash, and local_hash. When it
returns a conflict, stop and report it. Do not blindly resolve.
lira gh conflicts --json
lira gh conflicts show ORION-48 --json
lira gh diff ORION-48
cat ~/.lira/gh-cache/conflicts/ORION-48.diff
Resolve only after understanding which side should win:
lira gh resolve ORION-48 --prefer local --json
lira gh resolve ORION-48 --prefer remote --json
If a human is in the loop, surface the diff and affected fields, such as
["body"], and ask before choosing. If local has tasks or acceptance criteria
that the remote body lacks, --prefer local is often right, but confirm.
Search, Query, And Board
lira ls --project ORION --json
lira show ORION-48 --json
lira search "token refresh" --json
lira query --status in-progress --label rust --json
lira query --task-status blocked --json
lira query --task-tag sql --json
lira count --project ORION --group-by status --json
lira board --project ORION --json
lira active --agent athena-analyst --json
For large workspaces use --limit and --cursor to paginate. Do not fetch
everything when you only need a slice.
Validation And Recovery
lira doctor --json
lira validate --json
lira reindex --json
Run lira doctor after anything unusual: an interrupted command, a manual edit
by the user, suspected drift between status field and directory, or a GitHub
operation that errored mid-flight. The index can always be rebuilt; canonical
YAML cannot.
Filesystem Inspection
The directory layout is part of the product. When debugging or grepping, read it
directly:
ls ~/.lira/projects/ORION/tickets/in-progress/
cat ~/.lira/projects/ORION/tickets/in-progress/ORION-48.yaml
tail ~/.lira/logs/$(date -u +%Y-%m-%d).jsonl
ls ~/.lira/gh-cache/conflicts/
Read freely, but write only through lira commands. Direct writes bypass locks
(~/.lira/locks/), atomic-rename semantics, and the JSONL audit log.
Common Errors
Check error.error_code on JSON failures.
| Code | Response |
|---|
E_ACCEPTANCE_CRITERIA_REQUIRED | Add --acceptance-criterion flags and retry. |
E_TASK_REQUIRED | Add --task flags and retry. |
E_COMPLETION_POLICY | Drive remaining tasks to done or cancelled; do not auto-force. |
E_INVALID_TRANSITION | Run lira project show <P> --json to see allowed transitions for the current status. |
E_INVALID_TASK_STATUS / E_INVALID_TASK_SCHEMA | Re-read the task schema rules. Tasks have only six fields. |
E_LOCK_UNAVAILABLE | Another process holds the lock; wait briefly and retry, or run lira doctor if stale. |
E_CLAIM_HELD | Someone else owns the ticket. Do not start duplicate work unless the user approves a force claim. |
E_NO_CANDIDATES | No eligible local work matched the filters. Report that directly. |
E_BLOCKED_BY_DEPENDENCY | A non-terminal blocker prevents dispatch. Inspect links.blocked_by. |
E_WORKFLOW_FILE_NOT_FOUND / E_WORKFLOW_PARSE | The requested WORKFLOW.md helper could not read or parse runner config. |
E_UNSUPPORTED_TRACKER_KIND | The workflow helper only understands tracker.kind: lira. |
E_GH_NOT_INSTALLED / E_GH_AUTH | Local-only ops still work. Tell the user to install gh or run gh auth login. |
E_GH_CONFLICT | Three-way conflict; read the diff and ask the user before resolving. |
E_GH_RATE_LIMIT | Back off and retry later. Do not hammer. |
E_TICKET_NOT_FOUND / E_PROJECT_NOT_FOUND | Verify with lira ls or lira project list. |
E_INVALID_YAML / E_SCHEMA_VALIDATION | Run lira doctor; usually points at a file the user edited by hand. |
The full list is in lira_prd_v0_4.md section 18.3.
Anti-Patterns
Avoid:
- Editing ticket YAML directly.
- Removing ticket files directly; use
lira mv <ID> cancelled or
lira archive <ID>.
- Treating embedded tasks as separate tickets.
- Skipping
--json.
- Treating
lira candidates as a claim. It is only a read; always claim before
launching work.
- Forcing past the completion guard.
- Adding comments or assignees to tasks.
- Hand-editing
~/.lira/gh-cache/.
- Silently stealing a claim.
- Running
lira gh sync --all after a long hiatus without checking for
conflicts.
- Creating a parentless lira ticket when a Jira parent exists.
- Trying to write back to Jira.
lira jira fetch and lira jira sync-parents
only pull.
- Cramming a multi-scope Jira ticket into one lira ticket. Create siblings
sharing the same
--parent-jira instead.
- Expecting lira to run Codex, manage workspaces, or implement Symphony retry
loops. Use an external runner for that.