feat(api): message labels — PATCH + ?labels filter + SDKs/CLI/MCP#173
Open
jiashuoz wants to merge 2 commits into
Open
feat(api): message labels — PATCH + ?labels filter + SDKs/CLI/MCP#173jiashuoz wants to merge 2 commits into
jiashuoz wants to merge 2 commits into
Conversation
Slice A of the labels feature: backend end-to-end. Strings, not IDs;
delta semantics; reserved e2a:* prefix for server-applied system labels.
Adds:
- migration 020: labels TEXT[] NOT NULL DEFAULT '{}' on messages, plus
a GIN index. ALTER ADD COLUMN with a constant default is metadata-only
on Postgres 11+, safe on the large prod messages table.
- identity.ModifyMessageLabels(msgID, agentID, add, remove) — single
atomic UPDATE with set semantics (union ∪ add) \ remove. Pre-checks
the post-add count against MaxLabelsPerMessage (100) and returns a
typed ErrLabelLimitExceeded; cross-agent / missing rows return
ErrMessageNotFound.
- PATCH /api/v1/agents/{email}/messages/{id} accepts
{ add_labels, remove_labels }. Returns the post-update label set so
callers can echo state without a fetch.
- GET /api/v1/agents/{email}/messages?labels=urgent&labels=follow-up
AND-matches via @>. Cursor encodes the filter so a token issued with
one label set can't be replayed against a different one.
- MessageSummary and MessageDetail responses now include labels[],
always present (never null) — empty array for unlabelled rows.
- Validation: lowercase, [a-z0-9:_-]+, ≤64 chars, ≤50 per op,
≤100 per message, e2a:* reserved for system writes (filter still
permits reading them).
Tests (17):
- internal/identity/labels_test.go (7): round-trip, remove + overlap
semantics, cap rejection, NotFound, cross-agent isolation, AND-match
filter, never-null contract on freshly-created rows.
- internal/agent/labels_api_test.go (10): happy add/remove with
lowercasing, system-prefix rejection, charset rejection (space,
slash, unicode, empty), over-length rejection, per-op cap rejection,
404 on missing, 404 on cross-agent, 401, list filter AND match +
always-non-null labels in response shape, list filter invalid char.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice B of the labels feature: client surfaces. Wires the PATCH
endpoint + ?labels filter through every consumer.
- Generated: web/public/openapi.yaml, TS types, Python types (regen).
- TS SDK: api.updateMessageLabels(email, msgId, body) — raw HTTP.
client.updateMessageLabels(msgId, {addLabels, removeLabels, agentEmail})
— high-level. api.listMessages + client.listMessages accept
`labels: string[]` and emit repeated ?labels= query params.
- CLI: `e2a labels <msg-id> [--add <label> ...] [--remove <label> ...]`
with --agent override. Prints the post-update label set.
- MCP: new `update_message_labels` tool. `list_messages` accepts
`labels[]` for AND-match filtering. Tool registry test updated to
expect the new tool name in both stdio and http servers; positive
test asserts add/remove plumbing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Adds per-message labels: a single
labels TEXT[]column onmessages, aPATCH /api/v1/agents/{email}/messages/{id}endpoint with delta semantics (add_labels/remove_labels), and an AND-match filter?labels=urgent&labels=follow-upon the existing list endpoint. Strings, not IDs — AgentMail-shaped, not Gmail-shaped. Reservede2a:prefix for future server-applied system labels.Slices
Slice A — Backend Go end-to-end (commit
16442c1)labels TEXT[] NOT NULL DEFAULT '{}'+ GIN index. Metadata-only on Postgres 11+ (constant default), safe on prod-sizedmessages.identity.ModifyMessageLabels— atomic UPDATE with set semantics(union ∪ add) \ remove. Pre-checks the post-add count againstMaxLabelsPerMessage(100) and returns a typedErrLabelLimitExceeded. Cross-agent and missing rows returnErrMessageNotFound.PATCH /messages/{id}handler with label validation (lowercase,[a-z0-9:_-]+, ≤64 chars, ≤50 per op,e2a:*rejected on user writes — read OK).?labels=...&labels=...AND-match filter onGET /messages. Cursor encodes the filter so tokens can't be replayed across different label sets.MessageSummary.LabelsandMessageDetail.Labels— always present (never null), empty array for unlabelled rows.Slice B — Client surfaces (commit
c313267)api.updateMessageLabels,client.updateMessageLabels(...),listMessages({ labels }).e2a labels <msg-id> [--add <label> ...] [--remove <label> ...].update_message_labelstool,list_messagesacceptslabels[]. Tool registry tests updated + positive plumbing test.Design choices
add_labels/remove_labels), not full replacee2a:prefixe2a:auto-tagged,e2a:hitl-approved) without collision.Verification
internal/identity: 7 new tests + full suite greeninternal/agent: 10 new tests + full suite greenTest plan
?labels=filtermake migrate)🤖 Generated with Claude Code