Skip to content

Feature: Add allocation tools and a planning toolset - #481

Merged
ripexz merged 6 commits into
mainfrom
feature/allocation-tools
Aug 20, 2026
Merged

Feature: Add allocation tools and a planning toolset#481
ripexz merged 6 commits into
mainfrom
feature/allocation-tools

Conversation

@ripexz

@ripexz ripexz commented Aug 20, 2026

Copy link
Copy Markdown
Member

Description

Adds eight tools for resource-scheduler allocations — the planning plane that commits a person's time to a project, distinct from task estimates and from logged time:

list_allocations, get_allocation, create_allocation, update_allocation, delete_allocation, restore_allocation, link_task_to_allocation, unlink_task_from_allocation

Built on the typed allocation support added in twapi-go-sdk v1.23.1.

⚠️ users_workload moves toolset

The tools live in a new twprojects-planning sub-toolset, and users_workload moves into it from twprojects-people. The two are the allocated and the estimated plane of the same question, and answering "how loaded is this person" from one alone gets it wrong.

twprojects-planning is added to the project-manager and analyst profiles, so profile users are unaffected. Anyone pinning -toolsets=twprojects-people loses users_workload and needs to add twprojects-planning.

Behaviours the descriptions have to carry, because the response does not

Each was confirmed against a live site.

  • The list endpoint windows its own results. With neither date set it returns today through 30 days out and says nothing about having narrowed the range. On the site tested that is 27 allocations against 660 for an explicit 2020–2030 range — an unqualified call silently answers with 4% of the data. The tool sends no window of its own, since defaulting one would reorder results for every existing caller; the description tells callers to always pass both, and a test pins the omission on the query string.
  • show_deleted replaces the result set rather than adding to it: 307 allocations without it, 277 with, every one of those deleted.
  • linkedTaskEstimatedTime counts each linked task whole, and a task can sit behind several allocations, so it must not be summed across them.
  • The per-day rate is what is held constant, so extending an end date adds committed time rather than spreading the existing total.

Notable choices

  • seconds_per_day, not hours. Same quantity, but the hours form is a float and rounds.
  • inform_of_over_allocation defaults on. Without it a change that overruns someone's capacity is refused outright, leaving ignore_collisions as the only way through — and that one lets the change land while suppressing the over-allocation report, so nobody is told the person is now over-booked. The create and update results say so when the API reports it. Verified live: "Allocation created successfully with ID … Note that it puts the assigned user over their capacity for this period."
  • Delete is a soft delete and does not expose hardDelete, which is what leaves restore_allocation something to act on. Restore is not gated behind allowDelete: it undoes a deletion rather than performing one, and it recovers anything soft-deleted by any client — the site tested carries 277 allocations deleted through the UI. Gating it would also leave list_allocations able to find deleted allocations, being a read tool, with no way to act on them.
  • Forecasted revenue and cost sit behind an explicit include_financial_details opt-in, being entitlement- and permission-gated and withheld silently. canViewFinancialDetails on each row reports what actually happened.
  • distribute_type and recurring allocations are not exposed, pending backend support.

Tool-definition cost is +3,507 o200k tokens (+6.15%) (go run ./cmd/mcp-tokens -base=main), list_allocations being 1,338 of it. The rarely-useful project filters (projectHealths, onlyStarredProjects, hideObservedProjects, onlyProjectsWithExplicitMembership) are supported by the SDK but deliberately left off the tool.

Type of Change

  • New feature
  • Breaking change — users_workload moves out of twprojects-people

Testing

  • Tests pass locally (go test -v ./...)
  • Added/updated tests for new functionality

gofmt, go vet, golangci-lint and cmd/docs-gen are clean. Filters, ordering values, sideloads and write payloads are asserted on the wire rather than through the mocks' canned body, since a dropped parameter is otherwise indistinguishable from a working one. Results are validated against the published output schema, which nothing else does in-process.

Also exercised end to end against a live site through the STDIO server: create → get → sparse-field get → update → list → count_only → ordered list → restore, plus the sideloads arriving and the over-allocation report firing. Test data created during the run was removed.

Checklist

  • Code follows project style guidelines
  • Self-reviewed the code
  • Added necessary documentation
  • No new warnings or errors

🤖 Generated with Claude Code

@ripexz
ripexz requested a review from a team as a code owner August 20, 2026 09:51
ripexz added 6 commits August 20, 2026 11:56
Adds eight tools for resource-scheduler allocations — the plane of planning
that commits a person's time to a project, distinct from task estimates and
from logged time:

  list_allocations, get_allocation, count via count_only,
  create_allocation, update_allocation, delete_allocation,
  restore_allocation, link_task_to_allocation, unlink_task_from_allocation

They live in a new `twprojects-planning` sub-toolset alongside
`users_workload`, which moves out of `twprojects-people`: the two are the
allocated and the estimated plane of the same question, and answering "how
loaded is this person" from one alone gets it wrong. `twprojects-planning`
is added to the `project-manager` and `analyst` profiles so those keep the
workload tool, but anyone pinning `-toolsets=twprojects-people` has to add
the new toolset to keep it.

Three behaviours the tool descriptions have to carry, because the response
does not:

- The list endpoint windows its results to today through 30 days from today
  when neither date is given, and says nothing about having narrowed the
  range. The tool sends no window of its own — defaulting one here would
  reorder every existing caller's results — so the description tells callers
  to always pass both, and a test pins the omission on the query string.
- `linkedTaskEstimatedTime` counts each linked task whole, and a task can
  sit behind several allocations, so it must not be summed across them.
- The per-day rate is what is held constant, so extending an allocation's
  end date adds committed time rather than spreading the existing total.

`seconds_per_day` is the rate parameter rather than hours, since the hours
form is a float and rounds. Delete is a soft delete and does not expose
hardDelete, which is what leaves restore_allocation something to act on;
restore is gated behind allowDelete for the same reason. Forecasted revenue
and cost sit behind an explicit `include_financial_details` opt-in, as they
are entitlement- and permission-gated and withheld silently.

Tool-definition cost is +3,321 o200k tokens (+5.82%).
Three gaps found by comparing the tools against a real request and
response.

`inform_of_over_allocation` was missing, and it is the parameter that
matters most here. Without it a change that overruns the assigned user's
capacity is refused outright, leaving `ignore_collisions` as the only way
through — and that one lets the change land while suppressing the
over-allocation report with it, so nobody is told the person is now
over-booked. It is defaulted on, and the create and update results say so
when the API reports it; the report is the whole point of the flag, so
returning a clean success would be worse than not offering it.
`ignore_collisions` now documents that it takes precedence and what that
costs.

The sideloads a verbose read requests now arrive. `get_allocation` asked for
`include=projects,assignee` and then dropped both, because the typed
response modelled no sideloads — the query string was correct and the
related objects never appeared. Covered by a test that also validates the
result against the output schema the sideloads widened.

Writes send `linkedTaskIDs` rather than `linkedTaskIds`, matching the
response and the API's other clients.

`distributeType` and `linkedTaskLoggedTime` stay unmodelled rather than
being stripped from responses, so `list_allocations` still returns both in
its streamed body. That is fine because neither can be set from here, which
is now pinned by a test: an argument naming either is dropped before the
payload is built, in both naming conventions.
Picks up the typed allocation support the allocation tools are built on,
including the fix that sends a sideload list as one comma-separated
`include` parameter. Before it, a verbose read asked for the project and
assignee sideloads and received only the first.

The branch could not build standalone until this landed: go.mod still
resolved the SDK to a version with no Allocation type, and the tools were
developed against a local replace directive.
Both sideload assertions read `lastURL.Query()["include"]` and compared the
elements, which passes whether the sideloads travel as one comma-separated
parameter or as one parameter per sideload. The endpoint only honours the
former, so these tests accepted the encoding that silently returned the
first sideload and nothing else.

They now assert the raw query carries a single `include`, and check
membership within its value.
The parameter returns only deleted allocations; it does not add them to the
active ones. Against a live site the same window reports 307 allocations
without it and 277 with it, every one of those carrying status "deleted".

The description said "include deleted allocations in the results", which
reads as additive — so a caller could treat such a list as the full picture
and report nothing currently scheduled, or conclude an active allocation
does not exist.
Restore was gated behind allowDelete on the reasoning that it only applies
to a row a delete created. That was wrong: it acts on anything soft-deleted
by any client, and the staging site carries 277 allocations deleted through
the UI that it can recover. Confirmed against one of them, which the server
had never touched.

Gating it was also incoherent with the read surface. list_allocations is
always registered, so an agent could already find deleted allocations with
show_deleted and had no way to act on them. And restore is not destructive
in the first place — it undoes a deletion.

delete_allocation stays gated. cmd/mcp-stdio hardcodes allowDelete to
false, so nothing that removes an allocation ships today.
@ripexz
ripexz force-pushed the feature/allocation-tools branch from a8c555c to 8340e37 Compare August 20, 2026 10:03
@ripexz
ripexz merged commit e2500e2 into main Aug 20, 2026
4 checks passed
@ripexz
ripexz deleted the feature/allocation-tools branch August 20, 2026 10:12
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