Withdraw and ownership

~3 min read

Withdraw and ownership

The ownership model for proposals: who can do what to which proposals.

Ownership

Every proposal is owned by the agent (token) that created it, identified by that key’s label. There is no concept of transferring ownership — accept, reject, and withdraw all simply delete the proposal.

The owner is recorded as agentLabel in .lettuce/proposals/<id>.json:

{
  "id": "ab12cd34-...",
  "agentLabel": "Claude Desktop",
  "tool": "write_note",
  "timestamp": "2026-05-28T10:00:00Z"
}

What an agent can do

Each agent can:

This is enforced at the server level — the tools simply filter by the agentLabel of the calling token.

What a human can do

Through the proposals UI or CLI, you (the human) can:

Withdraw flow

The agent’s perspective:

list_my_proposals takes no arguments and returns only the calling key’s pending proposals as {id, tool, path, operation, timestamp}:

my_props = client.tools.list_my_proposals()
for p in my_props["proposals"]:
    if no_longer_valid(p):
        client.tools.withdraw_proposal(id=p["id"])

withdraw_proposal deletes the proposal file from .lettuce/proposals/. It vanishes from the proposals UI immediately. The id must be the lowercase UUID list_my_proposals returned; anything else is rejected before it reaches the store. The tool refuses to touch a proposal owned by another agent label.

The human’s perspective:

In the proposals UI, select a proposal and reject it. Reject deletes the proposal file just as withdraw does — there is no tombstone, history, or audit trail kept on disk.

What happens after accept

When you accept a proposal, Lettuce re-checks staleness, commits the proposal’s after content to disk through the same write path the live tools use (so attribution, soft-locks, link-rewrites, and reindex all stay consistent), and then deletes the proposal file from .lettuce/proposals/. There is no separate history store and no resulting-sha record kept on disk.

What happens after reject

Rejecting deletes the proposal file. There is no rejection note, no status field, and no retention window — the proposal is simply gone.

Because resolved proposals aren’t stored, an agent can’t query them after the fact. list_my_proposals only ever returns pending proposals it authored — once a proposal is accepted, rejected, or withdrawn, it disappears from that list. An agent learns nothing was applied by noticing the proposal is no longer in its queue, then re-reading the live file and reconciling.

A polite agent reconciles on every pass. Drop a hint in AGENTS.md:

If a proposal you filed is no longer in list_my_proposals, the user resolved it. Re-read the current file before re-proposing, and don’t blindly re-file the same suggestion.

Cleaning up

There is no history to prune — accept, reject, and withdraw each delete the proposal file outright, so .lettuce/proposals/ only ever holds pending proposals. Withdraw anything an agent no longer wants applied with withdraw_proposal(id).