/graphql.
By use case
These are the workflows agents actually run against this twin, each naming the MCP tools and GraphQL surfaces involved. Anything named here that is not fully modelled says so in place.Triage an issue
List backlog issues, read one, move it through workflow states, and update its assignee, priority, or estimate.save_issue omits dueDate and milestone over MCP; issueUpdate over raw GraphQL can set dueDate directly. MCP: list_issues, get_issue, save_issue. GraphQL: issues, issue, issueCreate, issueUpdate.
Comment and label an issue
Reply in a comment thread and attach a label to flag it for follow-up. MCP:list_comments, save_comment, delete_comment, list_issue_labels. GraphQL: comments, comment, commentCreate, commentDelete, issueAddLabel, issueRemoveLabel, issueLabels.
Browse projects and cycles
Read what a team is working on and when. MCP:list_projects, get_project, save_project, list_cycles, list_teams. GraphQL: projects, project, cycles, cycle, teams, workflowStates.
Read and write documents
List a team’s documents and create or update one.save_document omits initiative parents, and there is no GraphQL-layer document surface in this twin — document access is MCP-only. MCP: list_documents, get_document, save_document.
Run agent sessions and webhooks
Create an agent session on an issue or comment, log an activity against it, and register a webhook for delivery. GraphQL only, and there is no external delivery beyond logged attempts — no live Linear network calls. GraphQL:agentSessions, agentSession, agentSessionCreateOnIssue, agentSessionCreateOnComment, agentSessionUpdate, agentActivityCreate, webhooks, webhookCreate, webhookDelete.
Authenticate as an OAuth client
Exercise the same authorization-code flow a real Linear OAuth app would. The twin also accepts a seeded personal token for client parity without the round trip. GraphQL:GET /oauth/authorize, POST /oauth/authorize/callback, POST /oauth/token, POST /oauth/revoke.
What you can rely on
Each surface below carries two rulings that are deliberately kept apart: heat is how deep it should be, ruled per milestone; state is how deep it is today, measured by the twin’s own tests. Neither moves because the other did. A surface is in exactly one state, and the four below are different answers to “can I write a task against this”:@pome-sh/twin-linear declares 22 MCP tools and 45 GraphQL surfaces, read from the twin’s own fidelity.inventory.json (last updated 2026-07-22):
Shape only
Read these; do not grade on them. The response shape is right and the behaviour behind it is not asserted, so a criterion that checks a value here can pass for the wrong reason.Out of scope
The MCP families outside the tools above — initiatives, milestones, releases, attachments, git diffs and PR review, status updates, and agent skills — are not modelled, and GraphQL fields outside this inventory are refused with a loud GraphQL validation error (HTTP 400). Nothing outside that is given a named row here: a call to it reaches the twin’s catch-all and is refused loudly, never answered with a faked success.Vendor-only tool
The vendor declares it and the twin answersunknown_tool. An agent doing the right thing is marked down for it.
Where it runs. Both doors serve the same twin:
pome twin start linear boots it as a local process, and pome sandbox create --twin linear starts a hosted sandbox running the same image. The states above are properties of the twin, so they hold on either.
Everything above is what the twin declares. What it was measured to do is a different number: the comparison against real Linear runs daily and publishes, surface by surface, how many matched, which drifted, and which are ruled exceptions with the reason written out in full — at status.pome.sh/twin/linear, which also states when it last ran and how old the captured baseline it compares against is. No count is copied onto this page, because copying one is how it goes stale.
Read from FIDELITY.md, fidelity.inventory.json, LIMITS.md and REFERENCE-DIVERGENCES.md in pome-sh/digital-twins, at the commit .github/twins-ref pins (2e41939). LIMITS.md is the one to read before writing a task that pages, batches or attaches — every bound it names is enforced and fails loudly rather than truncating. REFERENCE-DIVERGENCES.md records which oracle each ruling was measured against.
Quickstart
Point your agent at it
For interactive development without a full task run, start the standalone twin:POME_LINEAR_REST_URL is the GraphQL root — the twin exposes it at /graphql. POME_LINEAR_MCP_URL is the MCP transport endpoint. POME_AUTH_TOKEN is the Pome JWT, and POME_LINEAR_TOKEN is an alias of it.
The default local user is admin@pome-twin.test, and the seed also provisions a personal token lin_test_admin. The twin accepts the Pome JWT, seeded Linear tokens, or lin_pome_* provider tokens as bearers.
Since it speaks GraphQL, point requests at /graphql with a query in the body:
GraphQL still exposes
issueCreate/issueUpdate for SDK parity, even though the
MCP surface uses save_issue.Task seed shape
Linear task seeds are flat, withorganization, users, teams, labels, projects, cycles, issues, comments and documents, plus oauthApps, tokens and webhooks. Each team carries its own states, and an issue’s state names one of them.
Generate it; do not copy it. The block below is what the twin starts with, printed by the CLI from the twin’s own declared state — so it parses against the twin you are about to seed, today and after the twin changes:
twins: ["linear"] in the task’s ## Config block and name the file <task>.seed.json, beside the task’s .md. That ## Config is the only place the twin gets named: the file above is flat, so it carries no twin id of its own.
Seed file
A task is not the only way in.pome twin start and pome sandbox create take that very same file — no wrapper, no second shape, nothing to convert — and each takes the twin’s name beside it, since the file cannot supply one:
pome twin new-seed linear slack writes it:
twin start and sandbox create want the name as well, and why a task’s sidecar does not — the ## Config beside it already named the twin. From two twins up the file is a per-twin envelope { <twin>: <seed> }, which does name its twins, though twin start still has to be told which one of them to boot. Replace, not merge, applies wherever the file lands.
Build your own world is that job end to end — generating the file, editing it down, both doors, and how to tell whether every field you wrote actually landed.
Example tasks
Ready-made examples you can run or copy to see the twin in action:Catalog
There is also a cross-twin handoff task,
26-github-linear-handoff.md, cataloged under GitHub.
Run one
pome-suggest-tasks and pome-run-task skills, installed by the graded capstone.