Skip to content

feat(witan): filter task_search by project and assignee - #494

Merged
blarghmatey merged 3 commits into
mainfrom
ia/task-search-filters
Oct 16, 2026
Merged

blarghmatey merged 3 commits into
mainfrom
ia/task-search-filters

Conversation

@blarghmatey

Copy link
Copy Markdown
Member

One teammate's insights report shows 18-20 malformed task_search / task_list calls per session while looking for "the next learner analytics task". task_search took only query, repo and status, while witan tasks "<query>" also takes --project and --assignee, so one likely cause is an agent reaching for a filter the tool doesn't have. The CLI offered those filters by narrowing the server's 20 hits client-side, which missed a project's match whenever it ranked below the unfiltered top 20.

This adds project_slug and assignee to task_search (witan task tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52):

  • A project search filters in the match (new search_tasks_by_project pair in read.gq) and spans every repo, the same way task_list(project_slug=...) ignores repo.
  • assignee (including "@me") filters in Python over a new uncapped search_tasks_all_uncapped pair, matched with _holder_matches as in task_list. Repo, status and the 20-hit cap apply after the filter. The unfiltered path still uses the existing capped queries, so task_search without filters and task_create's similar read the same rows as before.
  • witan tasks QUERY passes --project and --assignee through instead of filtering itself. --ready still intersects with task_ready.
  • The docstring points "what's next in project X" at task_ready / task_list(project_slug=...).
  • The witan-task skill gets a "Find a task" section naming the real CLI fallback, witan tasks "<query>" --project wp-... (the insights tool suggested witan task search, which does not exist), and one rule: if the same call is rejected twice, stop varying it and use the CLI.

Two limits I left alone. witan tasks QUERY --project wp-x without --status still drops closed rows after the server's 20-row cap, so a project with 20+ closed matches outranking an open one prints fewer rows than exist (this predates the branch, and the query help text now says so; the UI avoids it with one task_search per live status, and the CLI could do the same). An assignee-only search reads every BM25 match in the graph, uncapped and with descriptions, and filters repo/status/assignee in Python; assignee has to be filtered in Python because _holder_matches widens an unqualified name to every session, but repo/status could move into uncapped variants of the existing queries if the cost shows up. I did not measure it.

A newer CLI talking to a deployment older than this release will get a schema rejection for witan tasks QUERY --project/--assignee until the deployment is upgraded. A plain witan tasks QUERY still works there, because the proxy drops None arguments before sending (_map_args in witan_core/remote/proxy.py). That is from reading the code, not tried against a live deployment.

The original failure is not reproduced. The teammate's transcripts aren't available, so the actual rejected input is still unrecorded. The spec also notes that Claude Code refuses a call whose arguments don't parse as JSON before the server sees it, and resending the same input would loop the same way. The skill rule covers that case too, but this change can't tell which one it was.

Tested with pytest mcp/servers/witan (what just test-witan-council runs) against omnigraph 0.13.0: 1571 passed, 4 skipped, 1 failed. The failure is test_pre_upgrade_candidates_exclude_the_current_binary, which fails locally because pointing the suite at a non-default omnigraph binary puts two on PATH; it doesn't touch this code. New tests cover a project hit in another repo, assignee="@me", a project/assignee match that ranks past 20 on the unfiltered query, and witan tasks QUERY --project across repos. The existing CLI search tests (closed elision, the 50-row window, --assignee, --ready --status) pass unchanged. prek passes on the changed files and just docs-check is clean.

Not done yet: the manual check that asking a Claude Code session for "the next task" converges without a rejected call. That needs a live session, and the transcript excerpt should go on the task.

https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

One teammate's insights report shows 18-20 malformed task_search /
task_list calls per session. `task_search` took only query, repo and
status, while `witan tasks QUERY` also takes --project and --assignee,
so one likely cause (not reproduced) is an agent passing a filter the
tool did not have. The CLI offered those filters by narrowing the
server's 20 hits client-side, so a project's match ranked below the
unfiltered top 20 was never found.

`task_search` now takes both. A project search filters in the match and
spans every repo; assignee (including "@me") filters in Python over an
uncapped candidate read, and the 20-hit cap applies after either filter.
The CLI passes its flags through instead of filtering itself. The
docstring points "what's next in a project" at task_ready/task_list.

Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
No skill named task_search or a CLI fallback, and the insights tool told
people to add `witan task search`, which does not exist. The new "Find a
task" section maps "the next task in a project" to task_ready/task_list,
keywords to task_search with its real filters, and names
`witan tasks "<query>" --project wp-...` as the fallback once the same
call has been rejected twice.

Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
The witan UI's search comments said task_search takes no project, which
stopped being true with the previous commit; the reason the project view
reads the whole project (the 20-hit cap) still holds. The changelog
fragment stated the cause of the malformed calls as fact, which was not
reproduced.

Task: tk-task-search-malformed-mcp-calls-loop-18-20-times-66de52

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
Copilot AI balanced review requested due to automatic review settings October 15, 2026 21:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified in the filtering logic or CLI integration, and regression tests cover the principal new behaviors.

0 open findings

What changed in this PR

Adds project and assignee filters to Witan task search, aligning the MCP tool with existing CLI options and avoiding filtering only the top 20 hits.

Changes:

  • Adds server-side project and assignee filtering, including @me.
  • Passes CLI filters directly to task_search and adds regression coverage.
  • Updates search guidance, generated references, and release notes.
File Description
mcp/​servers/​witan/​witan/​skills/​witan-task/​SKILL.md Adds task discovery and CLI fallback guidance.
mcp/​servers/​witan/​witan/​server.py Implements filtering before the final result cap.
mcp/​servers/​witan/​witan/​cli/​tasks.py Forwards project and assignee filters to the server.
mcp/​servers/​witan/​ui/​src/​app.ts Updates comments explaining project search behavior.
mcp/​servers/​witan/​ui/​src/​app.test.ts Updates the project search test comment.
mcp/​servers/​witan/​tests/​test_tasks.py Tests cross-repo projects, @me, and lower-ranked matches.
mcp/​servers/​witan/​tests/​test_cli.py Tests project searches across repositories.
mcp/​servers/​witan/​queries/​read.gq Adds broader queries for filtered searches.
mcp/​servers/​witan/​docs/​CLI_REFERENCE.md Documents positional keyword search.
mcp/​servers/​witan/​changelog.d/​20261015_163802_blarghmatey_task_search_filters.md Records filters and deployment compatibility limits.
docs/​reference/​mcp-tools/​tasks.md Updates generated tool documentation.
docs/​reference/​cli.md Updates generated search-limit documentation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@blarghmatey
blarghmatey merged commit 4782460 into main Oct 16, 2026
20 checks passed
@blarghmatey
blarghmatey deleted the ia/task-search-filters branch October 16, 2026 16:59
blarghmatey added a commit that referenced this pull request Oct 16, 2026
…25 helper (#504)

#494 called _search_task_bm25(name, params), written against the helper
as it was before #500 collapsed the per-filter queries and changed it to
take one params dict. The two merged an hour apart, so on main a
task_search with project_slug or assignee raises TypeError, and three
test_tasks.py tests fail. main's push workflow runs only the ol-agent-kit
tests, so nothing caught it after merge.

The helper keeps the one-dict signature and takes the base query name as
an optional second argument, defaulting to search_tasks, and unions it
with its _title twin as before.

Claude-Session: https://claude.ai/code/session_015yBunUPNeYQ54pWBaqhCwM
blarghmatey added a commit that referenced this pull request Oct 16, 2026
`setup --agent codex` and the Codex /hooks trust notice come from
witan-core's AgentName and report_install, which change in this PR. Both
servers' witan-core floors admitted 0.42 (witan) and 0.41 (witan-code),
so upgrading a server while keeping an older core rejected
`--agent codex` and dropped the notice from `--agent all`. Both floors
move to >=0.43 as behaviour floors, and the three packages release
together so the floor exists on PyPI when the servers do.

witan-council 0.42.0 also collects every fragment pending since 0.41.0,
including #494, #495, #496 and #503.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
blarghmatey added a commit that referenced this pull request Oct 16, 2026
* fix(witan-code): reindex the files a Codex apply_patch edits

Codex's hooks.json accepts the `Edit|Write` matcher witan code setup
registers for reindex-hook, and selects its `apply_patch` tool by that
alias. The payload names the tool `apply_patch` and carries the patch
text as `tool_input.command`, with no file path, so the hook found
nothing to reindex once agent-config-kit writes witan's hooks to Codex.

reindex-hook now reads the patch's Add File, Update File and Move to
headers and reindexes each file the patch leaves on disk. Only those
header lines at column 0 count, so a path inside added content is not
picked up.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* feat(witan-core): print install notices and accept --agent codex

Since #493, agent-config-kit writes declarative hooks to Codex's
hooks.json and returns one notice per new hook, saying Codex won't run it
until the user trusts it with /hooks. report_install never printed
InstallResult.notices, so `witan setup` and `witan code setup` wrote
witan's UserPromptSubmit and Stop hooks to Codex with nothing telling
the user they sit inert until trusted.

report_install now prints each notice as an action line. `codex` joins
AgentName (labelled "Codex CLI"), so both setups can target Codex alone.

notices and the codex platform exist only in unreleased agent-config-kit
(0.12.0 has neither), so the cli extra's floor moves to >=0.13. That
also covers prerequisites, so its getattr fallback goes.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* fix(witan): close the session from Codex's Stop hook

witan session-checkpoint finds the session handle by the agent session
id, which it reads from $CLAUDE_SESSION_ID or $PI_SESSION_ID. Codex runs
hooks with its own process environment and no session variable, so
under Codex the checkpoint would find no handle and leave the session
open. The id is only in the hook's stdin payload.

The checkpoint now reads stdin and, for a Codex payload (told apart by
`turn_id`, which Codex's input schema marks as its extension), passes
the payload's session_id as the explicit id. A Claude Code payload is
not used, so the Claude path still resolves $CLAUDE_SESSION_ID.

The payload id is the one Codex exports to its shell tool as
$CODEX_SESSION_ID, so that variable becomes the last fallback in
current_session_id, and the workflow skills and workflow_session_start
docs tell a Codex agent to pass it.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* fix(witan-code): accept indented apply_patch file headers

Codex's patch parser trims a line before matching an Add File or Update
File header outside an update hunk, so an indented header applies but
was not reindexed. Move to is only right-trimmed there, so it stays
column-0. The reindex-hook docstring now describes the Codex payload as
well as Claude Code's.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* fix(witan): keep session-checkpoint's stdout empty under Codex

With a half-configured remote (e.g. WITAN_REMOTE_URL without
WITAN_OIDC_ISSUER), _srv() prints its error to stdout before raising
SystemExit. The checkpoint swallows the exit, but Codex reads any
non-JSON Stop hook stdout as a malformed decision and marks the hook
failed. The dispatch now runs with stdout redirected to stderr.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* fix(witan): drop an inherited CODEX_SESSION_ID from Pi's checkpoint child

The Pi extension's checkpointEnv clears CLAUDE_SESSION_ID and
PI_SESSION_ID before setting Pi's own id, so a Pi started from another
agent can't close that agent's session. This branch made
CODEX_SESSION_ID a fallback in current_session_id, and checkpointEnv
didn't clear it. A Pi launched from Codex's shell tool whose session
manager returns no id would then close the Codex session's handle.

The test reads checkpointEnv's source and requires a delete for every
variable in SESSION_ID_ENV_VARS, so adding a fourth can't repeat this.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* doc(witan): name Codex in the session-id fallback docs

task_claim's session_id docs, the holder docstring and the provenance
reader still listed only CLAUDE_SESSION_ID and PI_SESSION_ID. Codex is
in Pi's position: it exports CODEX_SESSION_ID only to its shell tool's
commands, so a local stdio witan never sees it and a Codex agent has to
pass the id. The session_state comment also said Codex hooks get no
session variable at all, when a hook inherits whatever launched Codex.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT

* version: witan-core 0.43.0, witan-council 0.42.0, witan-code 0.26.0

`setup --agent codex` and the Codex /hooks trust notice come from
witan-core's AgentName and report_install, which change in this PR. Both
servers' witan-core floors admitted 0.42 (witan) and 0.41 (witan-code),
so upgrading a server while keeping an older core rejected
`--agent codex` and dropped the notice from `--agent all`. Both floors
move to >=0.43 as behaviour floors, and the three packages release
together so the floor exists on PyPI when the servers do.

witan-council 0.42.0 also collects every fragment pending since 0.41.0,
including #494, #495, #496 and #503.

Claude-Session: https://claude.ai/code/session_015nBsjMvN2iuLbUbCSFpsWT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants