Skip to content

feat(claims): leased work-claim ledger so agents don't grab the same issue/PR - #141

Merged
getappz merged 6 commits into
masterfrom
feat/claim-ledger
Jul 11, 2026
Merged

feat(claims): leased work-claim ledger so agents don't grab the same issue/PR#141
getappz merged 6 commits into
masterfrom
feat/claim-ledger

Conversation

@getappz

@getappz getappz commented Jul 11, 2026

Copy link
Copy Markdown
Owner

What

Step 1 of the GitHub-across-agents roadmap: a leased work-claim ledger so
parallel AI agents don't both grab the same GitHub issue/PR. A claim = "owner
holds target (issue#N / pr#M) in repo".

How

  • Store — new ~/.agentflare/agentflare.db (SQLite, rusqlite) with a
    claims table PRIMARY KEY(repo, target). src/db.rs is the shared opener;
    gateway secrets fold in later (refactor(db): consolidate source-of-truth DBs into ~/.agentflare/agentflare.db #138). The rebuildable caches under
    ~/.local/share/agentflare/ stay separate.
  • Race-safety — acquire and stale-steal are one atomic UPSERT:
    INSERT ... ON CONFLICT(repo,target) DO UPDATE ... WHERE status='done' OR heartbeat_at < now-ttl OR owner=me. Two agents can never both own a target;
    a live claim by someone else changes 0 rows → reported as held. (A filesystem
    create_new lock can't make the steal atomic — this is why SQLite.)
  • Lease — a claim carries heartbeat_at; past the TTL (default 30 min,
    AGENTFLARE_CLAIM_TTL_SECS) another agent may steal it, so a crashed agent
    doesn't wedge a target. Re-acquiring your own claim refreshes it.
  • Identityowner = <agent>:<instance>: handoff's chain
    (AGENTFLARE_AGENTagent_detector::agent_namecli) + instance
    (AGENTFLARE_SESSION else pid). repo normalized from the origin remote to
    owner/name (handles https, ssh-alias, .git).

Surface

  • MCP (flare server): claim_acquire, claim_heartbeat, claim_release,
    claim_list.
  • CLI: agentflare claim {acquire|heartbeat|release|done|list} <target> [--repo].

Reference

Models Beads's SQLite-backed
claim/close model, minus the full issue-tracker surface (deps, JSONL git-sync)
— we're a lease over existing GitHub issues/PRs, not a tracker.

Tests

8 ledger unit tests (in-memory db): free→held, own re-acquire refreshes,
stale-steal vs fresh-blocked, done re-acquirable, owner-scoped
heartbeat/release/done, list hides stale/done, repo scoping, repo
normalization. Full suite: 268 passed, 0 failed.

Also driven end-to-end via the CLI (two agents, a real repo): acquire →
block-other → list → heartbeat → owner-scoped release denial → TTL steal after
a real time gap → ownership transfers.

Closes #139 · related #138 (db consolidation), #136/#137 (step 0)

Summary by CodeRabbit

  • New Features
    • Added a local SQLite work-claim ledger to coordinate exclusive ownership of GitHub issues and pull requests between competing agents.
    • Introduced a claim CLI with subcommands to acquire, heartbeat (refresh), release, mark done, and list claims, including repo auto-detection and status/staleness indicators.
    • Added MCP tools to perform the same claim lifecycle operations and to list claims, with configurable scoping across repos.
  • Bug Fixes
    • Enforced safeguards so only the current claim owner can heartbeat, release, or mark a claim as done.

…issue/PR

A claim = an owner holding a target (issue#N/pr#M) in a repo, stored in a new
SQLite ledger at ~/.agentflare/agentflare.db. Acquire and stale-steal are one
atomic UPSERT (ON CONFLICT ... WHERE done OR heartbeat<now-ttl OR owner=me), so
two agents can never both own a target and a crashed agent's claim is reclaimed
after the TTL. owner = <agent>:<instance> (handoff's agent chain + session/pid);
repo normalized from the origin remote to owner/name.

Surface: MCP tools claim_acquire/claim_heartbeat/claim_release/claim_list on the
flare server, plus `agentflare claim {acquire|heartbeat|release|done|list}`.
Adds src/db.rs (shared agentflare.db opener; gateway secrets fold in later, #138).

Closes #139
@getappz getappz added enhancement New feature or request rust Pull requests that update rust code labels Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a SQLite-backed work-claim ledger with TTL-based ownership, repository and agent identity helpers, persistent database access, CLI commands, MCP tools, and a 25-minute CI build timeout.

Changes

Work-claim ledger

Layer / File(s) Summary
Ledger schema and claim operations
src/claims.rs
Creates the claims table and implements atomic acquisition, heartbeats, release, completion, listing, stale-claim handling, and tests.
Claim identity and repository context
src/claims.rs
Adds owner, TTL, time, repository resolution, and URL normalization helpers.
Persistent database access
src/db.rs, src/main.rs
Adds the canonical database path, filesystem setup, SQLite connection tuning, migration execution, and module wiring.
Claim CLI commands
src/cli/claim.rs, src/cli/mod.rs
Adds acquire, heartbeat, release, done, and list commands with repository resolution, dispatch, provenance, and error reporting.
Claim MCP tools
src/mcp_server.rs
Adds MCP request types and tools for acquiring, maintaining, releasing, completing, and listing claims with validation and JSON responses.

CI timeout

Layer / File(s) Summary
Build job timeout
.github/workflows/ci.yml
Sets the CI build job timeout to 25 minutes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant ClaimInterface
  participant Database
  participant ClaimsLedger
  Agent->>ClaimInterface: request claim operation
  ClaimInterface->>Database: open and migrate ledger
  Database-->>ClaimInterface: SQLite connection
  ClaimInterface->>ClaimsLedger: acquire, heartbeat, release, done, or list
  ClaimsLedger-->>ClaimInterface: operation result
  ClaimsLedger-->>ClaimInterface: operation result
  ClaimInterface-->>Agent: CLI output or MCP JSON
Loading

Possibly related PRs

  • getappz/agentflare#60: Adds related modular CLI command-dispatch architecture used by the new claim subcommand.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The workflow timeout change in .github/workflows/ci.yml is unrelated to the claim-ledger scope in #139. Move the CI timeout tweak to a separate PR, or document it as a necessary supporting change for this feature.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a leased work-claim ledger to prevent duplicate claims.
Description check ✅ Passed It explains the change, rationale, interfaces, and tests well, though it doesn't follow the template's exact section headings.
Linked Issues check ✅ Passed The PR implements the SQLite claim ledger, lease rules, owner-scoped ops, MCP/CLI surfaces, and identity/repo handling required by #139.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/claim-ledger

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/claims.rs (1)

53-91: 🩺 Stability & Availability | 🔵 Trivial

Set a busy timeout in the shared opener Add busy_timeout in src/db.rs; this ledger is used by parallel agents, so contending writers can fail fast with SQLITE_BUSY. If the database is shared across processes, enable WAL there too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/claims.rs` around lines 53 - 91, Add SQLite concurrency settings in the
shared database opener in db.rs: configure a busy timeout so parallel writers
wait instead of failing immediately with SQLITE_BUSY, and enable WAL mode when
the database is shared across processes. Keep claims::acquire unchanged and
apply these settings centrally wherever the shared connection is opened.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/claim.rs`:
- Around line 67-70: Update the ClaimAction::Acquire flow and the corresponding
MCP acquire path to resolve the normalized provenance repository and commit
together: only retain git_commit() when that repository matches the selected
repository, and otherwise pass no commit provenance. Ensure --repo overrides
never attach HEAD from the current checkout.

In `@src/db.rs`:
- Around line 18-21: Update the database setup around Connection::open to create
the parent directory with 0700 permissions, propagate any create_dir_all error
instead of discarding it, and apply 0600 permissions to the database file after
opening it. Preserve the existing connection error propagation and use
platform-appropriate permission APIs.

In `@src/mcp_server.rs`:
- Around line 726-738: Expose a new claim_done MCP tool alongside claim_release
and the other claim tools, accepting the claim target and optional repository
context through ClaimTargetRequest. Resolve the claim context and current owner
via the existing helpers, invoke the claims completion operation while
preserving the audit row, map failures to ErrorData::internal_error, and return
a JSON response indicating whether completion succeeded along with the repo and
target.

---

Nitpick comments:
In `@src/claims.rs`:
- Around line 53-91: Add SQLite concurrency settings in the shared database
opener in db.rs: configure a busy timeout so parallel writers wait instead of
failing immediately with SQLITE_BUSY, and enable WAL mode when the database is
shared across processes. Keep claims::acquire unchanged and apply these settings
centrally wherever the shared connection is opened.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d900965-fe1b-4162-8f8a-53240424b953

📥 Commits

Reviewing files that changed from the base of the PR and between c09b89d and 0d1779b.

📒 Files selected for processing (6)
  • src/claims.rs
  • src/cli/claim.rs
  • src/cli/mod.rs
  • src/db.rs
  • src/main.rs
  • src/mcp_server.rs

Comment thread src/cli/claim.rs
Comment thread src/db.rs Outdated
Comment on lines +18 to +21
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let conn = Connection::open(path)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## src/db.rs\n'
wc -l src/db.rs
cat -n src/db.rs | sed -n '1,220p'

printf '\n## Search for related filesystem/setup code\n'
rg -n "create_dir_all|chmod|set_permissions|PermissionsExt|0600|0700|agentflare|Connection::open|open_with_flags" src . -g '!target' -g '!node_modules'

Repository: getappz/agentflare

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Call sites for src/db.rs::open()\n'
rg -n "db::open\(\)|agentflare_db_path\(\)" src

printf '\n## Relevant DB setup helpers\n'
cat -n src/auth_db.rs | sed -n '1,140p'
printf '\n---\n'
cat -n src/gateway_secrets.rs | sed -n '1,120p'

Repository: getappz/agentflare

Length of output: 11473


🌐 Web query:

SQLite new database file permissions umask Unix create database default 0644

💡 Result:

When SQLite creates a new database file on Unix-like systems, it uses a default permission mode of 0644 [1][2]. This default is governed by the SQLITE_DEFAULT_FILE_PERMISSIONS compile-time macro, which is set to 0644 by default [1][2]. Crucially, this mode is modified by the process's umask when the database file is initially created [1][3]. For example, if your system umask is 0022, the resulting file permissions will be 0644 (0644 & ~0022 = 0644) [4]. If you need to ensure different permissions (such as 0660 or 0600) for your database file and its associated auxiliary files (like -wal and -shm), there are three primary strategies: 1. Pre-create the file: Create the database file manually before initializing the SQLite connection [3][4]. Since the file already exists, SQLite will open the existing file rather than creating a new one with its own default permissions [5]. 2. Adjust permissions after creation: Use system utilities or programming interfaces (e.g., chmod) to update the file permissions after SQLite has created the file [6]. 3. Recompile SQLite: You can change the default by defining SQLITE_DEFAULT_FILE_PERMISSIONS at compile time (e.g., -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600) [7][8]. Note that for auxiliary files like -journal, -wal, and -shm, SQLite intentionally ignores the system umask to ensure these files maintain the same permissions as the main database file, which is critical for consistent access in environments where multiple processes need to recover or use the database [9][1]. Attempting to modify permissions using umask within the same process is generally discouraged because it affects the entire process, not just the thread handling the database [6].

Citations:


Restrict ~/.agentflare/agentflare.db permissions.
Connection::open(path) will create the DB with SQLite’s default Unix mode (0644, masked by umask), so this ledger can be left readable to other local users. Create ~/.agentflare with 0700, chmod the DB to 0600, and stop discarding create_dir_all errors so setup failures surface early.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/db.rs` around lines 18 - 21, Update the database setup around
Connection::open to create the parent directory with 0700 permissions, propagate
any create_dir_all error instead of discarding it, and apply 0600 permissions to
the database file after opening it. Preserve the existing connection error
propagation and use platform-appropriate permission APIs.

Comment thread src/mcp_server.rs
…atabase is locked'

Review follow-up on #141. A leased ledger is written by many agent processes at
once; with bundled SQLite's default busy_timeout of 0, a contended acquire
returned SQLITE_BUSY immediately and surfaced as an error instead of serializing
behind the current writer. Set a 5s busy_timeout (writers wait their turn) and
WAL (list reads proceed during a write). Also document that CLI ownership
continuity across separate invocations needs AGENTFLARE_SESSION (the pid
fallback differs per process; a long-lived MCP server is unaffected).
CodeRabbit follow-up on #141:
- Restrict agentflare.db to 0600 and ~/.agentflare to 0700 (was SQLite's
  umask-masked 0644) — matters once #138 folds gateway secrets in.
- Don't attach the current checkout's HEAD as claim provenance when --repo /
  repo= names a different repository (CLI + MCP).
- Add the missing claim_done MCP tool so agents can mark a claim complete
  (keeping its audit row) not just release it.
@getappz

getappz commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

All three review comments addressed in 2e463c5:

  • db permsagentflare.db now 0600, ~/.agentflare 0700 (unix, best-effort) in db.rs::open.
  • provenance on override — the current checkout's HEAD is attached only when the repo is auto-resolved, not when --repo/repo= names another repo (cli/claim.rs + mcp_server.rs).
  • claim_done — added the missing MCP tool (keeps the audit row, unlike release).

(The busy_timeout+WAL nitpick was already handled earlier in f2dbca1.)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,60p' src/db.rs
echo "---claim.rs---"
sed -n '1,160p' src/cli/claim.rs
echo "---mcp_server.rs claim section---"
rg -n "claim_" src/mcp_server.rs | head -50

Length of output: 8768


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '685,800p' src/mcp_server.rs

Length of output: 6281


All three fixes verified in the current diff — permissions, provenance-on-override, and the new claim_done tool all look correct and consistent with the described intent.

  • db perms: db.rs::open now creates ~/.agentflare with 0700 and restricts agentflare.db to 0600 via the Unix-gated restrict() helper (no-op on non-Unix). Applied on every open() call, which is fine since set_permissions is idempotent.
  • provenance on override: Both cli/claim.rs (let commit = if repo.is_none() { git_commit() } else { None };) and mcp_server.rs (claim_acquire capturing repo_overridden before claim_ctx consumes repo) correctly gate commit capture on whether the repo was explicitly supplied, so an explicit --repo/repo= no longer gets a misleading HEAD commit attached.
  • claim_done: The MCP tool is present at Line 742 of src/mcp_server.rs, mirrors claim_heartbeat/claim_release in structure, calls crate::claims::done (owner-scoped, keeps the audit row), and returns { "done": bool, "repo", "target" }.

Nice work — triggering a full review now to confirm nothing else needs attention.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@getappz
getappz enabled auto-merge July 11, 2026 12:18
shiva added 2 commits July 11, 2026 18:41
roundtrip_active_mode and clear_nonexistent_is_noop both read/write the same
process-global flag/session files. Cargo runs them on parallel threads, so
clear_nonexistent_is_noop's clear_active() could delete the flag file
roundtrip_active_mode depends on — a race that passed on Linux but panicked on
macOS and hung on Windows (blocking CI). Serialize both behind a Mutex.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(claims): work-claim ledger so agents don't grab the same issue/PR

1 participant