Repository navigation
🐛 fix(commitments): assign the user's own extracted tasks to the self node - #102
Conversation
… node Conversation extraction knew the user only by name, so a task the user took on could be assigned to a same-named Person. The dedup sweep that runs after every ingestion could also merge the flagged self node into an older Person with the same label, which dropped `isUserSelf` and moved every self assignment to a node that reads as someone else. - Conversation ingestion ensures the flagged self node in the source's partition and gives the model its ID as the author of role="user" messages, for subjects and for ASSIGNED_TO objects. - The transcript prompt tells the model to use a speaker's node as the ASSIGNED_TO object for tasks that speaker takes on. - The dedup sweep never merges the user-self Person. - A new self node is labeled "Me" until aliases name the user, instead of the raw user ID. Stored claims and labels are not rewritten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fca9c5f830
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…t attribution A readable "Me" placeholder made the self node the unique canonical-label match for any extracted Person named "Me", attributing that source to the user by label alone. The placeholder is the user id again, with a comment saying why. The ingestion guide and README now describe how conversation and transcript ingestion attribute the user's own words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6afd346037
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // its label and aliases from the stored profile; an existing one is left as | ||
| // is. | ||
| const [userSelfNodeId, userSelfAliases] = await Promise.all([ | ||
| ensureUserSelfPersonNode(db, userId, partitionKey), |
There was a problem hiding this comment.
Exclude the self node from label-based identity resolution
When a partition has no self node, this call creates and names one before extraction; _processAndInsertNewNodes then passes every model-emitted Person through resolveIdentity, whose unique canonical-label or alias signal can select that flagged node. If a role: "user" message mentions a different person sharing the owner's full name, the self node is hidden from prompt context, the model can mint that person, and the backend silently maps their claims or task assignments to the account owner. Fresh evidence in this revision is the new conversation integration test explicitly expecting a model-emitted same-full-name Person to collapse to the self node. Continue supplying the direct ID for first-person statements, but exclude flagged self nodes from generic label/alias resolution.
AGENTS.md reference: AGENTS.md:L3-L5
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changed; this needs an owner decision, so I'm leaving the thread open.
The test's "Marcel Samyn" task reaches the self node through the user's own configured self alias (userSelfAliases), not through a display label. The unnamed placeholder is the user id and can't match anything. Resolving an exact multi-word self alias to the self node is existing behavior that documents and transcripts already rely on (see docs/sdk/commitments.md#self-assignment). This PR only makes sure the node exists before conversation extraction.
Excluding the self node from alias resolution would:
- stop documents from resolving the user's full name to the user, and
- bring back the bug this PR fixes: when the model writes the user's name instead of the id it was given, the task would land on a duplicate same-name Person and show as owed by someone else.
The risk you describe is a namesake with exactly the user's full name, whose claims would be attributed to the user. That's rare but silent. A narrower option: skip alias resolution to the self node only in passes that received userSelfNodeId, on the reasoning that the model had the id and chose not to use it. That trades the namesake risk for the duplicate risk in conversations only.
🤖 Addressed by Claude Code
MinIO no longer publishes public images: quay.io/minio/minio returns 401 and Docker Hub has no minio/minio, so every PR's "Start MinIO" step fails. cgr.dev/chainguard/minio is MinIO built from source. /data is a volume so MinIO can rename inside its tmp folder at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tasks that the user takes on in a conversation are sometimes stored as owned by a Person node that has the user's name but is not the flagged self node. Commitment reads then show that Person as the owner, so Petals lists the user's own task under Waiting as "Marcel owes you". This PR fixes the cause for new ingestions.
Cause
There were two ways it happened:
isUserSelf). When a message said "I'll send the deck", the model assigned the task to a Person it matched or created by name. That node is an ordinary Person, andcommitmentOwnerIsSelfcorrectly treats it as someone else.isUserSelf, so every assignment to the self node now pointed at "someone else".Changes
ensureUserSelfPersonNodefor the source's partition and passes the node ID toextractGraph. The prompt names it as the author ofrole: "user"messages, for claim subjects and forASSIGNED_TOobjects.ASSIGNED_TOobject for tasks that speaker takes on.dedupSweepnever merges a node flaggedisUserSelf.UNNAMED_SELF_LABEL) until the user's aliases name them. It used to carry the raw user ID, which the model and the Explore UI then showed as a name.docs/sdk/commitments.mddescribe the behavior.What this doesn't fix
Stored claims and labels are not rewritten. Tasks already assigned to a duplicate "Marcel" Person stay that way. Petals has a manual fix for those: when an owner's name matches the user's, the task pane and row menu offer "That's me", which clears the owner (marcelsamyn-org/petals#423). An automatic repair would need a preview-then-apply flow, because a name match alone can't prove a node is the user. That is not in this PR.
How to test
role: "user"message says "I'll send Kai the deck tomorrow".listCommitmentsfor that user returns the new task withowner: null, because its owner is the self node.dedupSweepwith an older Person labeled like the user. The self node survives withisUserSelf: true.Checks
pnpm run build:check: passpnpm lint: passpnpm format: passpnpm test --runagainst a throwaway Postgres and Redis: 165 files, 1048 tests passed. New tests cover the self-node exclusion indedup-sweep.test.tsand the "Me" label inuser-self-identity.test.ts.🤖 Generated with Claude Code