Skip to content

feat(gateway): put lean-ctx behind the gateway, rename gateway_search/execute to tool_search/tool_execute - #184

Merged
getappz merged 1 commit into
masterfrom
feature/gateway-leanctx
Jul 14, 2026
Merged

feat(gateway): put lean-ctx behind the gateway, rename gateway_search/execute to tool_search/tool_execute#184
getappz merged 1 commit into
masterfrom
feature/gateway-leanctx

Conversation

@getappz

@getappz getappz commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Follow-up to a manual spike this session that proved viable: routing lean-ctx's ~80 ctx_* MCP tools through the agentflare gateway instead of declaring them natively in the host's tool list.

Changes

  1. src/gateway_integrations.rs: new LEANCTX GatewayIntegration (mcp_stdio backend, command = "lean-ctx") alongside the existing GITHUB one. Registers lean-ctx behind ~/.agentflare/gateway.toml on consent.
  2. src/components.rs: the "leanctx" component now (a) installs the binary as before, (b) registers it behind the gateway via gateway_integrations::register, and (c) for claude-code, removes any native mcpServers.lean-ctx entry lean-ctx's own onboarder created, via a new remove_claude_mcp_server helper — so the same tools don't end up declared twice. check() now requires both the binary being installed and gateway registration.
  3. Renamed gateway_search/gateway_execute MCP tools to tool_search/tool_execute (mcp_server.rs) — clearer, more obvious naming per user request. Updated all references/tests/doc-comments repo-wide, including stale mentions in crates/gateway-registry's doc comments.
  4. src/rule_text.rs: added a @fallback line to the lean-ctx usage rule pointing at tool_search/tool_execute for when ctx_* tools aren't natively present — old wording preserved in LEANCTX_SUPERSEDED so existing installs get an automatic refresh offer via the existing stale-rule mechanism.

Verified

  • Manually spiked this exact config live against my own session first: removed lean-ctx from claude-code's native MCP config, restarted, confirmed gateway_search/gateway_execute (pre-rename) discovered and executed lean-ctx's tools correctly end-to-end.
  • cargo test: 437/437 passed on a clean, uncontended run.
  • cargo clippy --all-targets -- -D warnings -A unsafe_code -A clippy::pedantic: clean.
  • cargo fmt --check: clean.

Known local flakiness (not from this diff)

worktree::tests::run_output_timeout_kills_the_child_not_just_abandons_it failed intermittently on later re-runs on this dev machine (2 of 3 attempts), but:

  • This branch never touches worktree.rs or agent_launch.rs.
  • It passed cleanly on the very first, uncontended run.
  • The failure signature (taskkill: process not found) is consistent with the test's polling thread getting starved under heavy local concurrent-build load (multiple simultaneous cargo test/clippy/check runs across worktrees on this machine during this session), not a functional regression — a real kill_tree bug would fail deterministically, not 1-in-3.
  • Leaving this to CI's clean, dedicated runners as the authoritative signal rather than touching test/impl code outside this PR's scope on a hunch.

Follow-up needed (separate, not in this PR)

Item #49 (opencode, uncommitted work on task/46) consolidates gateway_search/gateway_execute into a single gateway(action=...) dispatch tool — a different direction than this PR's simple rename. Left a review comment on #49 flagging the conflict; once this merges, that work should retarget tool_search/tool_execute (ideally as a single tool(action=...) dispatch, matching the rest of the action-dispatch family) instead of the old names.

Summary by CodeRabbit

  • New Features

    • Added LeanCtx as an integration available through the Agentflare gateway.
    • LeanCtx setup now supports Claude Code and removes duplicate native server configuration when applicable.
    • Added fallback guidance for discovering and executing LeanCtx tools.
    • Updated MCP discovery and execution tools to use tool_search and tool_execute.
    • Improved execution argument schema to clearly support object or null values.
  • Documentation

    • Updated audit, error, sanitization, truncation, and integration guidance to reflect the current tool naming.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR renames downstream MCP discovery and execution APIs to tool_search and tool_execute, updates related documentation, and adds leanctx as a gateway-managed integration with Claude Code cleanup and updated rule guidance.

Changes

Downstream tool API surface

Layer / File(s) Summary
Tool request contracts and routing
src/mcp_server.rs
Request types and router methods are renamed to ToolSearchRequest, ToolExecuteRequest, tool_search, and tool_execute; execution arguments now emit an object-or-null schema, with updated tests.
Tool API documentation references
crates/gateway-registry/src/*, crates/gateway-registry/tests/mcp_stdio_call.rs
Comments and module documentation now reference the renamed tool entry points.

Leanctx gateway integration

Layer / File(s) Summary
Leanctx integration registration
src/gateway_integrations.rs
Adds the LEANCTX integration, gateway registration configuration, consent text, post-install note, and registration tests.
Leanctx installation and native entry cleanup
src/components.rs, src/rule_text.rs
Installs leanctx behind the gateway, removes its native Claude Code MCP entry when present, tests cleanup behavior, and adds fallback rule guidance.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ComponentApply
  participant Leanctx
  participant AgentflareGateway
  participant ClaudeConfig
  ComponentApply->>Leanctx: install lean-ctx
  ComponentApply->>AgentflareGateway: register leanctx integration
  ComponentApply->>ClaudeConfig: remove native lean-ctx entry for claude-code
  AgentflareGateway-->>ComponentApply: return registration and cleanup status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly captures the main changes: routing lean-ctx behind the gateway and renaming gateway_search/execute to tool_search/tool_execute.
Description check ✅ Passed The description is detailed and covers the summary, verification, and reviewer notes, even though it doesn't use the exact template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/gateway-leanctx

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: 1

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

364-367: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded "leanctx" duplicates the idempotency key defined in gateway_integrations::LEANCTX.name.

Reference crate::gateway_integrations::LEANCTX.name instead of the literal string, so a future rename of the integration's key can't silently desync this check.

♻️ Proposed fix
             check: Box::new(|| {
                 crate::tool_install::installed(&crate::tool_install::LEAN_CTX)
-                    && crate::gateway_integrations::already_registered("leanctx")
+                    && crate::gateway_integrations::already_registered(crate::gateway_integrations::LEANCTX.name)
             }),
🤖 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/components.rs` around lines 364 - 367, Update the check closure in the
component definition to pass crate::gateway_integrations::LEANCTX.name to
already_registered instead of the hardcoded "leanctx" literal, while preserving
the existing installation check.
🤖 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/components.rs`:
- Around line 357-390: Update the lean-ctx installation flow in the apply
closure to distinguish a successful install from a prior failed attempt: write
the completion marker only after install succeeds, and allow subsequent runs to
retry when the installed binary is still missing. Register LEANCTX and remove
the native Claude entry only after confirming the tool is installed, so failed
installations cannot produce a dangling gateway registration or success message.

---

Nitpick comments:
In `@src/components.rs`:
- Around line 364-367: Update the check closure in the component definition to
pass crate::gateway_integrations::LEANCTX.name to already_registered instead of
the hardcoded "leanctx" literal, while preserving the existing installation
check.
🪄 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: 3a094837-df06-4631-b70f-9421963077d7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b0d19c and e597f85.

📒 Files selected for processing (11)
  • crates/gateway-registry/src/audit.rs
  • crates/gateway-registry/src/error.rs
  • crates/gateway-registry/src/mcp_stdio.rs
  • crates/gateway-registry/src/sanitize.rs
  • crates/gateway-registry/src/search.rs
  • crates/gateway-registry/src/truncate.rs
  • crates/gateway-registry/tests/mcp_stdio_call.rs
  • src/components.rs
  • src/gateway_integrations.rs
  • src/mcp_server.rs
  • src/rule_text.rs

Comment thread src/components.rs
Comment on lines +357 to +390
// supported tool it detects natively — exactly the always-on
// tool-list bloat the agentflare gateway exists to avoid. Right
// after installing, register it behind the gateway instead
// (`gateway_integrations::LEANCTX`) and, for claude-code, strip
// whatever native entry the upstream onboarder already created so
// the same ~80 ctx_* tools aren't declared twice.
describe: "lean-ctx (context compression) — native installer (curl | sh, or brew), registered behind the agentflare gateway (tool_search/tool_execute), not the host's native tool list".to_string(),
check: Box::new(|| {
crate::tool_install::installed(&crate::tool_install::LEAN_CTX)
&& crate::gateway_integrations::already_registered("leanctx")
}),
apply: {
let log = leanctx_log.clone();
let host = host_owned.clone();
Box::new(move || {
if log.exists() {
return format!("lean-ctx install already triggered — check {}", log.display());
}
let _ = fs::create_dir_all(log.parent().unwrap());
let outcome = crate::tool_install::install(&crate::tool_install::LEAN_CTX);
let _ = fs::write(&log, format!("{:?}", std::time::SystemTime::now()));
match outcome {
Ok(m) => format!("{m} + onboarded"),
Err(e) => e,
let mut msg = if log.exists() {
format!("lean-ctx install already triggered — check {}", log.display())
} else {
let _ = fs::create_dir_all(log.parent().unwrap());
let outcome = crate::tool_install::install(&crate::tool_install::LEAN_CTX);
let _ = fs::write(&log, format!("{:?}", std::time::SystemTime::now()));
match outcome {
Ok(m) => m,
Err(e) => return e,
}
};
msg = format!(
"{msg} + {}",
crate::gateway_integrations::register(&crate::gateway_integrations::LEANCTX)
);
if host == "claude-code" && remove_claude_mcp_server("lean-ctx") {
msg = format!("{msg} + removed native claude-code MCP entry (now gateway-only)");
}
msg

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gateway registration proceeds even after a previously-failed lean-ctx install, masking the failure as success.

The leanctx-install.log is written unconditionally right after calling install(), regardless of outcome. On the run where install fails, return e correctly skips registration — but on every subsequent run, log.exists() is true, so apply() skips reinstalling and falls straight through to gateway_integrations::register(&LEANCTX) (which doesn't verify the binary exists) and the claude-code cleanup. The result: gateway.toml gets a [servers.leanctx] entry pointing at a binary that was never actually installed, apply() reports it as "ok ... registered", and check() keeps failing forever with no way to retry the install short of manually deleting the log file. The gateway will fail at spawn time the first time a ctx_* tool is actually invoked.

🛡️ Proposed fix — don't register a dangling entry when the binary is still missing
                     match outcome {
                         Ok(m) => m,
                         Err(e) => return e,
                     }
                 };
+                if !crate::tool_install::installed(&crate::tool_install::LEAN_CTX) {
+                    return msg; // install genuinely failed previously — don't register a dangling gateway entry
+                }
                 msg = format!(
                     "{msg} + {}",
                     crate::gateway_integrations::register(&crate::gateway_integrations::LEANCTX)
                 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// supported tool it detects natively — exactly the always-on
// tool-list bloat the agentflare gateway exists to avoid. Right
// after installing, register it behind the gateway instead
// (`gateway_integrations::LEANCTX`) and, for claude-code, strip
// whatever native entry the upstream onboarder already created so
// the same ~80 ctx_* tools aren't declared twice.
describe: "lean-ctx (context compression) — native installer (curl | sh, or brew), registered behind the agentflare gateway (tool_search/tool_execute), not the host's native tool list".to_string(),
check: Box::new(|| {
crate::tool_install::installed(&crate::tool_install::LEAN_CTX)
&& crate::gateway_integrations::already_registered("leanctx")
}),
apply: {
let log = leanctx_log.clone();
let host = host_owned.clone();
Box::new(move || {
if log.exists() {
return format!("lean-ctx install already triggered — check {}", log.display());
}
let _ = fs::create_dir_all(log.parent().unwrap());
let outcome = crate::tool_install::install(&crate::tool_install::LEAN_CTX);
let _ = fs::write(&log, format!("{:?}", std::time::SystemTime::now()));
match outcome {
Ok(m) => format!("{m} + onboarded"),
Err(e) => e,
let mut msg = if log.exists() {
format!("lean-ctx install already triggered — check {}", log.display())
} else {
let _ = fs::create_dir_all(log.parent().unwrap());
let outcome = crate::tool_install::install(&crate::tool_install::LEAN_CTX);
let _ = fs::write(&log, format!("{:?}", std::time::SystemTime::now()));
match outcome {
Ok(m) => m,
Err(e) => return e,
}
};
msg = format!(
"{msg} + {}",
crate::gateway_integrations::register(&crate::gateway_integrations::LEANCTX)
);
if host == "claude-code" && remove_claude_mcp_server("lean-ctx") {
msg = format!("{msg} + removed native claude-code MCP entry (now gateway-only)");
}
msg
// supported tool it detects natively — exactly the always-on
// tool-list bloat the agentflare gateway exists to avoid. Right
// after installing, register it behind the gateway instead
// (`gateway_integrations::LEANCTX`) and, for claude-code, strip
// whatever native entry the upstream onboarder already created so
// the same ~80 ctx_* tools aren't declared twice.
describe: "lean-ctx (context compression) — native installer (curl | sh, or brew), registered behind the agentflare gateway (tool_search/tool_execute), not the host's native tool list".to_string(),
check: Box::new(|| {
crate::tool_install::installed(&crate::tool_install::LEAN_CTX)
&& crate::gateway_integrations::already_registered("leanctx")
}),
apply: {
let log = leanctx_log.clone();
let host = host_owned.clone();
Box::new(move || {
let mut msg = if log.exists() {
format!("lean-ctx install already triggered — check {}", log.display())
} else {
let _ = fs::create_dir_all(log.parent().unwrap());
let outcome = crate::tool_install::install(&crate::tool_install::LEAN_CTX);
let _ = fs::write(&log, format!("{:?}", std::time::SystemTime::now()));
match outcome {
Ok(m) => m,
Err(e) => return e,
}
};
if !crate::tool_install::installed(&crate::tool_install::LEAN_CTX) {
return msg; // install genuinely failed previously — don't register a dangling gateway entry
}
msg = format!(
"{msg} + {}",
crate::gateway_integrations::register(&crate::gateway_integrations::LEANCTX)
);
if host == "claude-code" && remove_claude_mcp_server("lean-ctx") {
msg = format!("{msg} + removed native claude-code MCP entry (now gateway-only)");
}
msg
🤖 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/components.rs` around lines 357 - 390, Update the lean-ctx installation
flow in the apply closure to distinguish a successful install from a prior
failed attempt: write the completion marker only after install succeeds, and
allow subsequent runs to retry when the installed binary is still missing.
Register LEANCTX and remove the native Claude entry only after confirming the
tool is installed, so failed installations cannot produce a dangling gateway
registration or success message.

@getappz
getappz merged commit b993eb2 into master Jul 14, 2026
14 checks passed
@getappz
getappz deleted the feature/gateway-leanctx branch July 14, 2026 18:46
getappz added a commit that referenced this pull request Jul 14, 2026
getappz added a commit that referenced this pull request Jul 18, 2026
…refix, fix flaky nanoid test

Review of task/184 (item #186) found the PR didn't actually cover its own
motivating case: item_get -- the exact call from #184's bug report
(item(get, id="178")) -- was never wired to resolve_item_id, and
resolve_id's numeric parse didn't strip a leading '#', so #-prefixed
sequence ids silently fell through to the UUID passthrough branch instead
of resolving, despite both the commit message and the tool's own schema
description claiming that support.

- item_get now resolves through resolve_item_id, closing the original gap
- resolve_id strips a leading '#' before parsing as numeric
- Added the tests #184 explicitly required and that were missing: bare
  numeric, #-prefixed numeric, not-found numeric, project-scoped lookup,
  end-to-end via the item MCP tool
- Fixed a flaky test: handoff_tool_requires_recipient_and_assigns_item
  asserted a filename via item_id.to_lowercase(), which doesn't match
  production's actual AgentflareMcp::slugify() transform -- slugify also
  collapses '_' to '-', which to_lowercase() doesn't, so the assertion
  failed whenever a randomly-generated nanoid id happened to contain an
  underscore. Now asserts against the real transform.

cargo test --workspace (632 passing, 0 failed), cargo fmt --check, and
cargo clippy --workspace --all-features -D warnings all clean.
getappz added a commit that referenced this pull request Jul 18, 2026
…tion to nanoid (#257)

* item/claim: accept numeric sequence_id or #-prefixed id; switch id generation to nanoid

item/claim MCP tools (get, update, update_state, delete, claim, heartbeat,
release, done, add_label, remove_label; claim's target param) now accept
either a UUID or a numeric sequence_id (bare or #-prefixed), resolved via
agentflare_backend::item::resolve_id scoped to the repo's linked project.
Not-found numeric ids return the same not-found shape as an unmatched UUID.
Closes #184.

Also switches db_kit::ids::new_id() from uuid::Uuid::now_v7() to
nanoid::nanoid!(), updating every caller across agentflare-artifacts and
agentflare-backend (asset/comment/label/project/state/webhook/workspace).

cargo build --workspace --all-features, cargo test --workspace (630+
passing across the bin plus every crate), cargo fmt --check, and cargo
clippy --workspace --all-features -D warnings all clean (the one remaining
clippy hit is the pre-existing Windows-only agent_launch.rs test import,
tracked separately as item #169, unrelated to this change).

* item/claim: wire item_get through sequence_id resolution, support #-prefix, fix flaky nanoid test

Review of task/184 (item #186) found the PR didn't actually cover its own
motivating case: item_get -- the exact call from #184's bug report
(item(get, id="178")) -- was never wired to resolve_item_id, and
resolve_id's numeric parse didn't strip a leading '#', so #-prefixed
sequence ids silently fell through to the UUID passthrough branch instead
of resolving, despite both the commit message and the tool's own schema
description claiming that support.

- item_get now resolves through resolve_item_id, closing the original gap
- resolve_id strips a leading '#' before parsing as numeric
- Added the tests #184 explicitly required and that were missing: bare
  numeric, #-prefixed numeric, not-found numeric, project-scoped lookup,
  end-to-end via the item MCP tool
- Fixed a flaky test: handoff_tool_requires_recipient_and_assigns_item
  asserted a filename via item_id.to_lowercase(), which doesn't match
  production's actual AgentflareMcp::slugify() transform -- slugify also
  collapses '_' to '-', which to_lowercase() doesn't, so the assertion
  failed whenever a randomly-generated nanoid id happened to contain an
  underscore. Now asserts against the real transform.

cargo test --workspace (632 passing, 0 failed), cargo fmt --check, and
cargo clippy --workspace --all-features -D warnings all clean.
getappz pushed a commit that referenced this pull request Aug 25, 2026
Discovery tick dispatches purely on the ready-for-work label, so items
#184/#185/#186/#187 (go/no-go candidates from #166's spec) whose own
description says "Decision pending — not dispatched" got auto-dispatched
and re-dispatched across multiple agents anyway -- the prose was never
actually enforced.

Add a needs-decision label that blocks run_discovery_tick even while
ready-for-work is also present. Stripping ready-for-work alone wouldn't
have been durable: redispatch unconditionally re-attaches it, so the new
label has to keep gating on its own until a human clears it.

Agentflare-Agent: claude-code
Agentflare-Branch: fix/dispatch-failure-ceiling-any-reason
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant