Skip to content

Create missing root or nested OmniFocus tags while assigning them #128

Description

@deverman

User outcome

Let a user create a missing root or nested OmniFocus tag at the moment it is
assigned to tasks or projects.

In UAT, the user asked to classify inbox items with tags such as Reference,
Ideas, and Contact. FocusRelay could only attach existing tag IDs, forcing
the user to leave the workflow, create tags manually, look them up, and retry.
OmniFocus itself supports creating and assigning a tag from the tag picker in one
user action.

Validation impact and dependencies

User-facing acceptance journey

  1. The assistant classifies selected inbox items and proposes tags.
  2. It queries tag IDs and hierarchy through Add parent-aware tag search and hierarchy to list_tags #70.
  3. For each missing tag, the user can accept a root location or choose an
    existing parent tag.
  4. Preview identifies tags that will be reused or created, their full paths, and
    any mutually exclusive sibling that OmniFocus will replace.
  5. After approval, each missing tag is created once, assigned to all successful
    targets, and verified without removing unrelated tags.

Public interface

Extend the tag mutation shared by edit_tasks, edit_projects,
add_tasks, and add_projects with:

{
  "addOrCreate": [
    { "name": "Reference" },
    { "name": "Contact", "parentID": "people-tag-id" }
  ]
}

Rules:

  • Omitting parentID means the root tag collection.
  • A supplied parent must already exist. Recursively creating a missing parent
    hierarchy is outside v1.
  • Existing ID-based add, remove, set, and clear behavior remains
    unchanged.
  • addOrCreate may be combined with ID-based add.
  • Reject addOrCreate combined with set or clear, because replacement
    semantics would otherwise be ambiguous.
  • For CLI parity, add a repeatable --tag-add-or-create-json option whose value
    is one {"name":"...","parentID":"..."} object. The parent is optional for a
    root tag.

Parent-scoped resolution

For each requested {name,parentID?} placement:

  1. Trim the name and reject an empty result.
  2. Resolve only among direct children of the specified parent, or only among
    root tags when no parent is supplied.
  3. Compare names case-insensitively:
    • one match: reuse it;
    • no match: create it at the end of that parent;
    • multiple matches: fail and return candidate IDs and full paths.
  4. Identical names under different parents are valid and are not ambiguous.
  5. Resolve or create each unique placement once per request, then reuse its
    stable ID for every target.
  6. Reject a missing or dropped parent before mutation.
  7. Permit an on-hold parent but preview that its hierarchy may affect task
    availability.

Use the documented Tag constructor with the selected parent's insertion
location. Do not reconstruct hierarchy from names when a stable parent ID is
available.

Mutually exclusive tag groups

If the selected parent reports childrenAreMutuallyExclusive == true:

  • inspect each target's currently assigned siblings;
  • preview the sibling tag that native OmniFocus assignment will remove;
  • use native assignment behavior rather than emulating replacement;
  • verify and return the complete final tag set;
  • do not claim unrelated tags were preserved when native mutually exclusive
    behavior correctly removed a sibling.

Preview, verification, and results

Preview performs no mutation and returns, for each requested placement:

  • normalized name;
  • parent ID or root;
  • full path;
  • reuse or create;
  • mutually exclusive replacement warnings.

The write result returns:

  • created/reused tag ID;
  • parent ID;
  • full path;
  • created/reused status;
  • per-target mutation and verification results.

Verification checks both tag existence at the expected parent and assignment to
every successful target.

Failure, compensation, and retry behavior

  • Validate every target, parent, ambiguity, and payload rule before creating a
    tag.
  • If a tag is created but no target assignment succeeds, remove that new unused
    tag. Report both the original failure and any compensation failure.
  • If some targets succeed, keep the tag and report each target independently.
  • A retry resolves and reuses the already-created parent-scoped tag rather than
    creating a duplicate.
  • Never report success when creation, assignment, save, or verification fails.
  • Preserve unrelated existing tags except for documented mutually exclusive
    replacement.

Out of scope

  • Standalone tag taxonomy administration.
  • Recursive creation of multiple missing ancestor tags.
  • Renaming, moving, dropping, or deleting existing tags.
  • Guessing a parent from a duplicated tag name.
  • Fuzzy or semantic tag resolution beyond Add parent-aware tag search and hierarchy to list_tags #70.

Test plan

Use Swift Testing and direct MCP wire coverage for:

  • create and assign a root tag to tasks and projects;
  • create a child under an existing parent;
  • reuse an existing root or nested tag;
  • at least three levels of hierarchy;
  • the same child name under different parents;
  • duplicate siblings under one parent failing without mutation;
  • missing and dropped parent rejection;
  • on-hold parent preview warning;
  • mutually exclusive sibling replacement in preview and verification;
  • preservation of unrelated tags;
  • request-level de-duplication of repeated placements;
  • preview-only behavior;
  • partial target failure and unused-tag compensation;
  • retry without duplicate creation;
  • CLI/MCP parity;
  • reversible live create, assign, verify, restore, and remove.

Add controlled Kimi K2.7 Code and comparison-model journeys. The model must query
#70 when the parent is ambiguous, supply a stable parent ID for nested creation,
and avoid extra tool calls when the root location is explicit.

Record first tool/operation choice, schema errors, retries, total calls,
end-to-end latency, final correctness, serialized catalog bytes, and token
estimates. Catalog size is diagnostic evidence, not a reason to reject a more
reliable user experience.

Acceptance criteria

  • A user can create and assign a missing root or nested tag in one approved
    workflow.
  • Parent-scoped duplicate handling is deterministic and never guesses.
  • Mutually exclusive groups have truthful preview and verification.
  • Retrying cannot create another tag for the same placement.
  • Preview, compensation, per-target results, and failure reporting are explicit.
  • The feature reuses existing add/edit commands and does not add a public tool.
  • Direct MCP, Swift, model-routing, and reversible live tests pass.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions