feat: add body modification and ETag concurrency control#57
feat: add body modification and ETag concurrency control#57matleh wants to merge 3 commits intohmans:mainfrom
Conversation
- Add ETag() method to Bean using FNV-1a 64-bit hash of rendered content - Include etag field in JSON output via custom MarshalJSON - Add etag field to GraphQL schema and regenerate resolvers - Add --if-match flag to beans update command for optimistic locking - Add --etag-only flag to beans show for easy etag extraction - Add ErrConflict error code for etag mismatch scenarios - Add comprehensive tests for ETag functionality ETag enables concurrent modification detection: ETAG=$(beans show <id> --etag-only) beans update <id> --status completed --if-match "$ETAG" On conflict, returns CONFLICT error with current etag.
- Add --body-replace-old/--body-replace-new for partial text replacement
- Add --body-append for appending content (supports stdin with -)
- Add helper functions: applyBodyReplace, applyBodyAppend, resolveAppendContent
- Update mutual exclusivity rules for body operation flags
- Update documentation with usage examples
- Add comprehensive tests for body modification functions
Enables atomic operations combining metadata and body changes:
beans update <id> \
--body-replace-old "- [ ] Task" --body-replace-new "- [x] Task" \
--status completed \
--if-match "$ETAG"
Body modification flags are mutually exclusive with --body/--body-file.
|
Hi, thanks for the PR! I have a question: what use case are you aiming at with this? If this is about support for multi-agent workflows, I intend to go into another direction (enabling the GraphQL API via HTTP and instructing agents to coordinate via that.) If it's not about support for multi-agent workflows, what are your reasons for having the |
|
Hi @hmans, My motivation: use beans in repos where I don't want to store
Actually, my current main feature is the The etag/if-match feature is more of a first safeguard to prevent agents working in parallel from clobbering each other's bean updates - actually, I haven't used it yet. To be really useful, I would add a config option |
|
If you want, I can split it up into two PRs - one about body-replace-old/new and another about etag/if-match. |
|
I'll need to give this some thought. I appreciate your input and your efforts here, but these are some non-trivial changes, and I need to make sure that they are something that I want to support in future versions. This is complicated by how this PR and also its motivations conflate a number of concerns: Using Beans without committing: I think my recommendation here would be to just put Multi-agent workflows: I want to fully bet on the GraphQL-over-HTTP approach I mentioned above for this, and this PR doesn't expose the new functionality in the GraphQL schema at all (with the exception of adding Safety-net style features (basic OCC): I like this part very much actually, including the etag mechanism. In fact I would, from the top of my head, fully bet on this or a similar mechanism: have I'll give this some more thought; if you want to submit a separate PR that implements the etag mechanism only and makes it available via both GraphQL as well as the Cobra CLI commands, I would happily merge it. |
|
superseded by #59 |
## Summary Adds CLI flags and GraphQL mutations for partial body modifications, enabling agents to update bean content without direct file access. Builds on #59 (ETag support) and supersedes #57. ## Changes ### CLI: Body Modification Flags - `--body-replace-old` / `--body-replace-new` for exact text replacement (must match exactly once) - `--body-append` for appending content (supports stdin with `-`) - Mutual exclusivity with existing `--body`/`--body-file` flags ### GraphQL: Partial Body Mutations - `replaceInBody(id, old, new, ifMatch)` - replace exactly one occurrence of text - `appendToBody(id, content, ifMatch)` - append content with blank line separator - Both support `ifMatch` for optimistic locking ### Shared Logic - Extract `ReplaceOnce` and `AppendWithSeparator` to `internal/bean/content.go` - Reused by both CLI and GraphQL resolvers ### Documentation - Add GraphQL API section to README with examples ## Usage ```bash # CLI: Check off a task beans update <id> --body-replace-old "- [ ] Task" --body-replace-new "- [x] Task" # CLI: Append notes beans update <id> --body-append "## Notes\n\nSome notes" # GraphQL: Check off a task beans query 'mutation { replaceInBody(id: "bean-xxx", old: "- [ ] Task", new: "- [x] Task") { body } }' # GraphQL: Append content beans query 'mutation { appendToBody(id: "bean-xxx", content: "## Notes") { body } }' ``` ## Why This Matters for Agents Agents can now modify bean bodies entirely through CLI/GraphQL without needing: - Direct file access to the `.beans/` directory - Knowledge of where beans are stored (which can be configured outside the repo) - Special file permissions ## Testing - Comprehensive tests for shared logic in `internal/bean/content_test.go` - GraphQL resolver tests for both mutations - CLI tests updated for new behavior --------- Co-authored-by: Hendrik Mans <[email protected]>
Motivation
The Problem: Beans Folder Outside Repository
The beans data directory can be configured to live outside the repository (via
.beans.yml). When this happens, agents struggle because:.beans/in the repo root (wrong location)Previously, modifying a bean's body required direct file access:
The Solution: CLI-Based Body Modifications
The new flags allow agents to modify bean bodies entirely through the CLI:
Benefits:
Concurrency Control
When multiple agents work concurrently, changes can be lost. The
--if-matchflag enables optimistic locking using ETags, and--etag-onlymakes extraction simple:Atomic Operations
By integrating into
update, agents can combine body and metadata changes atomically with a single etag check.Changes
Commit 1: ETag Support
--etag-onlyflag onbeans showfor easy extraction--if-matchflag onbeans updatefor optimistic lockingetagfield in all JSON output and GraphQL queriesCommit 2: Body Modification Flags
--body-replace-old/--body-replace-newfor partial text replacement--body-appendfor appending content (supports stdin with-)--body/--body-fileflagsUsage
Testing