How it works
Agents reach trackxai over MCP at /mcp, signed in with your browser or with a token. You reach it at /w. Both read and write the same rows, so what an agent filed is what you see.
A write, step by step
- The agent calls
task.createwith a title, a goal, an out-of-scope slot and at least one acceptance criterion. - An unknown key is refused before any rule runs.
- The hard-fail rules run. One failure and nothing is stored; the refusal names the slot, states the rule and gives an example that passes.
- The warning rules run. A warning never refuses. It is attached to the task and every later read shows it.
- The task is stored as fields, one per slot, so a filter and a search can address a slot by name.
- The response carries the task rendered as markdown, so no second read is needed.
call task.create
{
"title": "Reject an importer row with no legacy identifier",
"goal": "The importer refuses a row the mapper cannot address",
"acceptanceCriteria": ["A row with an empty legacy_id is rejected", "…"],
"out-of-scope": absent
}answer isError: true
The write was refused and nothing was persisted.
- slot "out-of-scope": a task carries out-of-scope, and whitespace alone reads as absent. Example: Backfilling rows already imported. That is a separate migration
Two classes of rule that never mix
A hard-fail rule is mechanical. A machine settles it with no judgement, and two readers reach the same verdict.
| Rule | Test |
|---|---|
| Required slot absent | Missing, empty, or whitespace only |
| No acceptance criterion | A task carries zero |
| Dangling reference | The target identifier resolves to no entity |
| Wrong reference ends | The relation does not admit those two entity kinds |
| Self-reference | Source and target are the same entity |
| Gap without a kind | The kind is absent or outside the three |
| Cross-workspace write | The payload names an entity in another workspace |
A warning rule is judgement. A reasonable reader could disagree, so it never rejects.
| Rule | Test |
|---|---|
| Goal restates the title | The two match, ignoring case and surrounding whitespace |
| Unverifiable criterion | The criterion contains no observable and no bound |
| Missing file | A files entry names a path absent from the linked repository |
A refusal an agent cannot act on in the same turn is a defect in trackxai, and the rule set is held to that.
Stored as fields, read as markdown
Every slot is a column. Every read renders the task as a markdown document: the title as the heading, then ## Goal, ## Out of scope, ## Acceptance criteria, ## Gaps, ## References, ## Files, ## Notes, ## Estimate. Empty optional sections are left out.
One task measured 1,290 characters as markdown against 1,898 as tags, 32% less to read. The rendering is pinned by a golden test, so its shape does not drift under an agent.
# Reject an importer row with no legacy identifier
## Goal
The importer refuses a row the mapper cannot address, and says which column was empty
## Out of scope
Backfilling rows already imported. That is a separate migration
## Acceptance criteria
- [x] A row with an empty legacy_id is rejected and the response names legacy_id
- [ ] `pnpm importer:check` exits 0 over the client export
## Gaps
- needs-input: Which column does the legacy importer read? The answer is in the importer source
- open-question: Postgres or SQLite for the row cache. Needs a call. Resolved: Postgres. The tenant scope is already there
- assumption: The upstream export is idempotent on retry
## References
- realizes epic epc_example, The importer refuses unaddressable rows
- blocks task tsk_example, Backfill the legacy_id column
- sources linear ACME-412 (https://linear.app/acme/issue/ACME-412)
## Estimate
4.5 work-hours
References
| Relation | From | To | Means |
|---|---|---|---|
realizes | task | epic | This task is part of delivering that epic |
blocks | task | task | The source must close before the target can open |
blocked-by | task | task | The stored inverse of blocks. One row, read either way |
supersedes | task, epic | task, epic | The source replaces the target, which keeps its history |
relates-to | any entity | any entity | Weak association, kept because forcing a precise relation produces a wrong one |
sources | project, epic, task | external | The client's system, its identifier and its URL |
verifies | criterion | goal | Written on the criterion itself, through its own verifies field |
blocks and blocked-by are one stored row read in either direction, so the pair cannot drift.
Gaps
A gap is something the task does not know, recorded without stopping.
| Kind | Obliges | Example |
|---|---|---|
needs-input | Somebody fetches a fact, and the gap names where | Which column does the legacy importer read? The answer is in the importer source |
open-question | You decide. The only kind that waits on a person | Keep the old export format for one more release. Needs a call |
assumption | Nothing. The belief is written down so it can be falsified later | The upstream export is idempotent on retry |
A gap is answered once, with the answer, who gave it and when. Answering an answered gap is refused. Numbers run from 1 per task and are never reused.
The claim lifecycle
Four task states: open, claimed, in-progress, done. task.claim is refused while another actor holds a live claim, and the refusal names the claimant. A claim goes stale after a configured window, thirty minutes by default, and a stale claim can be taken over, with both actors recorded.
task.complete refuses while an acceptance criterion is unticked, unless the call names every unticked criterion. Naming them records the decision as a comment.
Fan-out › TRX-142
Add per-channel idempotency
Your side
A task list with filters in the URL and saved views on a keystroke. A board of assigned work in three columns, Assigned, In progress and Done, that you move cards across from the keyboard, a Backlog of what nobody has been given yet, the ticket floating over it at the board's own address. An inbox of what waits on you, the epics, the open questions, and agent tokens issued and revoked in place, the plaintext shown once.
Activity reads newest first, for the workspace and on every task. The progress page cuts a day into your hours, or seven days into one row each.
The list refreshes on an interval and whenever you return to the tab. Once you are signed in, the button at the top of this site reads Open app and takes you straight to your workspace.
Board
DisplayAssigned14
Board order survives a different session
Collapse the sidebar to an icon rail
Name the blocker on a blocked card
In progress9
Add per-channel idempotency
Answer a gap by its number, inline
Done131
Refuse a claim while another is live
Render a task as markdown on read
Fan-out › TRX-142
Add per-channel idempotency
? Waiting on you, because a gap on it is waiting for you to answer.
- Epic
- Fan-out
- Project
- Notifications
- Blocks
- TRX-151
- Sources
- linear ACME-412
Gaps 1 unanswered
Goal
One send per user, channel and event, even when the queue retries
Out of scope
Retry policy. New channels.
Acceptance criteria 1/2
In the application
Captures of a demo workspace. Each numbered badge in a capture matches the line under it.
The board

- The board shows every task as a card, grouped by status.
- Switch between the board, the backlog and the full list.
- Tasks an agent is working on right now sit in In progress.
- The board shows every task as a card, grouped by status.
A ticket

- A task keeps its title, address and description in one place.
- Claim or complete the task from this row.
- A gap is a question an agent raised. Answer it here to unblock the work.
- A task keeps its title, address and description in one place.
Waiting on you

- Waiting on you lists the tasks blocked on your answer.
- Answer an open gap on a row without leaving the list.
The progress page

- The bar splits every task by phase: done, in progress, waiting, claimed or open.
- Open tasks group by epic. Select one to read it on the right.
- A question waiting on you shows up in the task's timeline.
- The bar splits every task by phase: done, in progress, waiting, claimed or open.
The documents list

- Filter documents by what owns them: the workspace, an epic, a team or a project.
- Open a document to read it. Each one shows its version.
- Open a document to read it. Each one shows its version.
Who can do what
Everyone you invite holds one of four roles in a workspace. The People page lists who holds which, who let them in and when, and a revoked membership stays on the list as history.
| Role | Can |
|---|---|
| Owner | Everything, including billing. The account's owner, on every workspace the account holds |
| Admin | Everything but billing: invites people, changes their roles, manages settings |
| Member | Reads and writes |
| Viewer | Reads |
An admin grants member or viewer. Only the owner grants admin. An agent acts through its own token, in the one workspace it was issued for.
What the gate guarantees
Presence, resolution and shape: every required slot filled, every reference pointing at something real, every task carrying a way to be checked. Substance stays with the reader, on purpose. The measurement behind the design found that 50 to 85% of review findings are substance no format touches, so trackxai spends its refusals where a machine is always right and shows you the rest as warnings.
Where to go
- Ready to point an agent at it: Connect an agent
- Where the edges are: What it does not do
- A workspace of your own: Pricing