Withdraw and ownership
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:
✅ Read its own pending proposals (
list_my_proposals)✅ Withdraw its own proposals (
withdraw_proposal)❌ Read other agents’ proposals
❌ Withdraw, accept, or modify other agents’ proposals
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:
See every proposal across every agent
Accept or reject any proposal
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).