Versions
Sep 17, 2026Claude CodeCurrent
Item numbering
Tasks, memories and documentation pages take their numbers from one sequence per project. #48 therefore names exactly one item, and item_get(projectId, number) returns it together with its kind. No typed reader has to be chosen first.
A bare #N only means something together with a project. Unknown and inaccessible numbers return the same message, so a caller learns neither the kind nor the title of an item it may not read.
Qualified references
| Form | Example | Use |
|---|---|---|
| Bare | #48 |
Inside one project, e.g. in task descriptions |
| Project | project:<project-id>#48 |
Outside the project context; the Reference of a project memory |
| Non-project memory | user:memory:<id> |
Personal, organization and system memories (scope first) |
The project form uses the project id, not its slug, so renaming a project breaks no reference. Personal, organization and system memories stay outside the sequence and are never inferred from a bare number. Project instructions have no number; tools address them with ref=instructions.
Allocation
ProjectItemService reserves the next number inside the writing transaction, under a lock on the project row (Projects.NextItemNumber), and registers the item in ProjectItems. The registry key (ProjectId, Number) enforces uniqueness across kinds. ItemNumberConcurrencyTests checks this against SQL Server, because SQLite serialises writers anyway and would hide the problem.
A page proposal gets its number only when it is approved. A rejected proposal never uses one up.
Numbers are never reused
Archived, closed and permanently deleted items keep their registry entry; a deleted one gets the kind deleted. An old #N in a commit message or a task description therefore never resolves to a newer item.
Numbers from before the shared sequence
Tasks and memories were once numbered separately. The migration to one sequence kept every task number, kept memory numbers that did not collide and moved colliding memories to free numbers above the previous maximum. The old memory number is kept as LegacyNumber, and an old link /projects/<project>/memories/<old-number> still opens that memory. Markdown written before the migration is not rewritten: an old #1 could have meant either kind.
Addresses for pages
In the web app a page lives at /projects/{project}/docs/{number}/{path}, where the path is made of the slugs of its ancestors and its own title. Renames and moves change the path, never the number, and outdated paths redirect. See Web routes (#289).
Sep 17, 2026Claude Code
Item numbering
Tasks, memories and documentation pages take their numbers from one sequence per project. #48 therefore names exactly one item, and item_get(projectId, number) returns it together with its kind. No typed reader has to be chosen first.
A bare #N only means something together with a project. Unknown and inaccessible numbers return the same message, so a caller learns neither the kind nor the title of an item it may not read.
Qualified references
| Form | Example | Use |
|---|---|---|
| Bare | #48 |
Inside one project, e.g. in task descriptions |
| Project | project:<project-id>#48 |
Across projects and in exports |
| Typed | task:project:<project-id>#48 |
Diagnostics |
| Non-project memory | user:memory:<id> |
Personal, organization and system memories |
The project form uses the project id, not its slug, so renaming a project breaks no reference. Personal, organization and system memories stay outside the sequence and are never inferred from a bare number. Project instructions have no number; tools address them with ref=instructions.
Allocation
ProjectItemService reserves the next number inside the writing transaction, under a lock on the project row (Projects.NextItemNumber), and registers the item in ProjectItems. The registry key (ProjectId, Number) enforces uniqueness across kinds. ItemNumberConcurrencyTests checks this against SQL Server, because SQLite serialises writers anyway and would hide the problem.
A page proposal gets its number only when it is approved. A rejected proposal never uses one up.
Numbers are never reused
Archived, closed and permanently deleted items keep their registry entry; a deleted one gets the kind deleted. An old #N in a commit message or a task description therefore never resolves to a newer item.
Numbers from before the shared sequence
Tasks and memories were once numbered separately. The migration to one sequence kept every task number, kept memory numbers that did not collide and moved colliding memories to free numbers above the previous maximum. The old memory number is kept as LegacyNumber, and an old link /projects/<project>/memories/<old-number> still opens that memory. Markdown written before the migration is not rewritten: an old #1 could have meant either kind.
Addresses for pages
In the web app a page lives at /projects/{project}/docs/{number}/{path}, where the path is made of the slugs of its ancestors and its own title. Renames and moves change the path, never the number, and outdated paths redirect. See Web routes (#289).
Sep 16, 2026Claude Code
Item numbering
Tasks, memories and documentation pages take their numbers from one sequence per project. #48 therefore names exactly one item, and item_get(projectId, number) returns it together with its kind.
Allocation
ProjectItemService reserves the next number inside the writing transaction, under a lock on the project row (Projects.NextItemNumber). Two agents writing at the same moment never receive the same number. ItemNumberConcurrencyTests checks this against SQL Server, because SQLite serialises writers anyway and would hide the problem.
A page proposal gets its number only when it is approved. A rejected proposal never uses one up.
What stays outside the sequence
Personal, organization and system memories belong to no project and are addressed by their own reference. Project instructions have no number either; tools address them with ref=instructions.
Numbers are never reused
A permanently deleted item keeps its entry in ProjectItems with the kind deleted. An old #N in a commit message or a task description therefore never resolves to a newer item. It resolves to nothing, and never reveals the old title.
Addresses for pages
In the web app a page lives at /projects/{project}/docs/{number}/{slug}. The slug is derived from the current title, so a rename changes it while the number stays. Links with an outdated slug, a bare number or only a slug redirect to the current address. A slug alone is ambiguous when two pages share a title; the number is what identifies a page.
To remove text that must not be kept, publish a corrected version first, then delete the older ones.