Repository navigation
fix(witan): delete one edge exactly and batch per-slug reads with GQ 2.1 - #488
Merged
Merged
Conversation
Before omnigraph 0.12 a mutation `where` took one predicate, so `_unlink_edge` deleted every edge on one endpoint and re-inserted the others. A concurrent reader could see them missing, and a failure between the delete and the re-inserts lost them. GQ 2.1 takes a compound `where`, so each edge kind now has one delete on `@src` and `@dst`, and the ten one-endpoint deletes and five inbound listings go. `in` on a list parameter also replaces the per-slug reads in the blocker checks (task_create, task_ready, task_unlink, the post-close unblock pass), workflow_project_get_blockers, recall's contradiction pass and the topic backfill. Each now issues one read, or one per bounded chunk for the backfill. Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
The blockers are now read in one `in` query, whose row order the engine does not promise, so the order comes from `blocked_by`. Nothing checked that: the existing test compares a set. The new test feeds an order no insert sequence produces, with a repeat, and asserts it comes back as is. Also corrects the comment on get_workflow_project_by_slug, which still described the per-blocker loop this branch removed. Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unbounded task-status batches can exceed local process argument limits, failing after task closure commits but before dependents reopen.
1 open finding
What changed in this PR
Uses GQ 2.1 to make Witan edge removal precise and reduce per-slug reads.
Changes:
- Replaces endpoint-wide deletion and reinsertion with exact edge deletes.
- Batches blocker, contradiction, and topic lookups.
- Adds regression coverage and a changelog fragment.
| File | Description |
|---|---|
| mcp/servers/witan/witan/server.py | Uses exact deletes and batched reads. |
| mcp/servers/witan/tests/test_workflow.py | Tests blocker ordering and duplicates. |
| mcp/servers/witan/tests/test_tasks.py | Tests cross-repo blockers and edge preservation. |
| mcp/servers/witan/queries/read.gq | Adds batch queries and removes unused inbound listings. |
| mcp/servers/witan/queries/mutations.gq | Deletes edges using both endpoints. |
| mcp/servers/witan/changelog.d/20261015_181500_blarghmatey_omnigraph_013_gq21_compound_where.md | Documents deletion and batching changes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
On the local transport a query's params go to the CLI as one --params argument, and Linux refuses a single argument of 128 KiB or more with E2BIG. _task_statuses sent every external blocker in a repo at once, so a large enough set would fail task_close after the close had committed and before dependents were reopened. recall's contradiction read had the same exposure through its uncapped limit. Both now go through _read_by_slugs, which the topic backfill already did inline, at 200 slugs per read. recall's query matches only the source end and filters the target locally, so a pair split across chunks is still found. Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What are the relevant tickets?
Witan task tk-use-gq-2-1-compound-where-or-and-in-to-remove-ed-5f0b32, part of the omnigraph 0.13 feature adoption. Items 1 (edge unlink) and 3 (per-id reads) of that task. The rest is recorded on the task (see the end of this description).
Description (What does it do?)
Before omnigraph 0.12 a mutation
wheretook one predicate, so_unlink_edgedeleted every edge on one endpoint and then re-inserted the others. A concurrent reader could see those edges missing, and a failure between the delete and the re-inserts lost them. GQ 2.1 takes a compoundwhere, so each edge kind now has one exact delete,delete Blocks where @src = $from and @dst = $to. The ten one-endpointunlink_*_by_{to,from}mutations and the five inbound*_from_slugslistings go._unlink_edgeis now one existence read (soremovedis still reported) and one delete.$x.slug in $slugsreplaces per-slug reads:_task_statusesreads the status of a set of tasks in one query. It replaces oneget_taskper blocker intask_create,task_ready(blockers outside the rows it already scanned),task_unlinkand the unblock pass aftertask_close.workflow_project_get_blockersreads all blockers at once and keeps theblocked_byorder.recall's contradiction pass reads the Contradicts edges among the returned memories in one query (contradicts_among) instead ofcontradicts_outper memory. Pairs are now ordered by the returned memories' order, not by edge creation time.witan migrate topics) reads existing Tagged edges in chunks of 200 memories instead of one read per memory, because the list is an inline parameter.Not in this PR.
orcannot join twosearch()predicates on 0.13.0 (lint refuses it with T38), so the search title twins stay. The per-filter-combination queries look collapsible with($repo is null or $m.repo = $repo), which lints, but I have not checked how it behaves. Dropping the bridge'srepo_filekey is a schema change on every deployed code-bridge graph.How can this be tested?
Both
.gqfiles lint clean withomnigraph linton 0.13.0 (the two WorkflowTrace warnings in mutations.gq were already there). I ran the witan-council suite against the 0.13.0 binary: 1564 passed, 4 skipped. The one failure,test_pre_upgrade_candidates_exclude_the_current_binary, comes from my harness putting two omnigraph binaries on PATH and is unrelated. Thetest_unlink_*tests in test_tasks.py exercise the new delete, including a case where both endpoints have other edges that must survive. A new test covers the unblock pass with a remaining blocker in another repo, andtest_get_project_blockers_keeps_blocked_by_ordercovers the order guarantee, including a repeated entry. An independent review probedin [], duplicate and missing slugs, and the exact delete on a scratch 0.13.0 store, and found nothing blocking. Its two low findings (a stale comment in read.gq and the missing order test) are fixed in the second commit.https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ