Hi, I've been playing around with the code, implementing custom features for a private fork, and seeing how many edge cases and ad-hoc fixes for the different backends there are, made me think that it's crazy that this was built almost entirely by a single person... huge shoutout @andrasbacsai :).
Anyway, a few days ago Agent Client Protocol (ACP, by Zed and JetBrains) announced support for effort modes. Until then, having Jean interface with the CLIs was the only way to take advantage of all the features, and all those edge cases and workarounds were inevitable.
However, now that ACP supports all the needed features, I wanna propose moving away from that architecture and using ACP instead. This would entirely remove all the backend-specific implementations. Every available option will be exposed by the SDK with a single standard for a bunch of different backends (including Claude, Codex, Cursor, and ~30 other providers).
Adding support for a new backend is just a matter of adding 3 lines of code to a single file:
pub enum AcpProvider {
+ Claude,
}
pub fn adapter_argv(self) -> &'static [&'static str] {
match self {
+ AcpProvider::Claude => &["npx", "--yes", "@agentclientprotocol/claude-agent-acp@0.31.0"],
}
}
pub fn as_str(self) -> &'static str {
match self {
+ AcpProvider::Claude => "claude-acp",
}
}
No other changes whatsoever.
Whenever a new Claude model is released, or some new feature is implemented (e.g., effort level for one model is different than effort level of another model), all you need to do is update the package version, which will pick up the update:
pub fn adapter_argv(self) -> &'static [&'static str] {
match self {
"npx",
"--registry",
"https://registry.npmjs.org",
"--yes",
- "@agentclientprotocol/claude-agent-acp@0.30.0",
+ "@agentclientprotocol/claude-agent-acp@0.31.0",
}
}
No other changes in your code. Neither backend nor frontend.
You can even use the ACP Registry directly, which automatically exposes all the available agents, so you don’t even need to do anything (not even releasing a new version of the app) when a new agent version is released or a new provider is onboarded to ACP.
In theory, that providers.rs file could be the only place where "claude" is referenced in the whole repository (other than `CLAUDE.md :) ). In practice I think it still makes sense to have provider-specific logic, for example the preferences page with the Claude skills (available only for Claude), but that doesn't affect the logic of the rest of the code in any way.
I started looking into this after noticing some issues with the permission flow. Implementing a fix for one thing broke another, and I felt like I was adding too many fixes/workarounds for things that someone else should fix or implement the workarounds for (which is exactly ACP).
The main cons are:
- This is a huge refactoring. Even though (see PR linked below) it can live next to the current CLIs implementation almost without affecting existing files.
- Implement migration logic whenever you're ready to make the switch between CLI-specific and ACP. If not implemented, users will lose the ability to access legacy chats. TBH this is not such a big of a deal since you can just replay the history to a new ACP session. Also, it can be a gradual rollout (e.g., first you add ACP separately as an experimental feature, then you enable it in production so that new chats use ACP but old chats still use CLIs, and finally - maybe after a couple of months when most legacy sessions will be archived - migrate legacy chats to ACP sessions and remove all the Claude/Codex/Cursor-specific code).
- New features depend on ACP. If Claude releases a new feature to the Claude CLI but not to Claude SDK (which is what happened with efforts), Jean will have to wait for Claude SDK to implement it and then ACP to pick up the change. Another example is AskUserQuestions, which is currently disabled in ACP for Claude due to some bugs in the implementation that made it flaky. But TBH this seems to be a bit flaky in Jean too (for the same reasons it was flaky in ACP), and in this case I believe it's actually a good thing that ACP entirely handles the set of stable features and Jean just relies on that, instead of having implementations that sometimes don't work correctly.
I tried to integrate ACP in Jean and I'd say it's extremely stable, which TBH is the thing I'm interested in the most. All the other features are extremely useful and nice to have, but I think having a very stable core chat experience should be the highest priority, and now that effort mode is available I think that makes it compatible with all Jean's already-available features (except for the AskUserQuestion UI which, TBH, it's nice to have but not a dealbreaker).
IMHO the pros outweigh the cons.
A proof-of-concept is available here if you want to try it out: #337. I you think this might be helpful, I'm happy to split this huge PR into smaller, well-tested, ones.
Also BTW, ACP is not just a standard to allow interfacing editors (Jean) with agents, but it also allows to interface agents with clients. For example, after making some changes to the code to expose Jean's APIs (which are already exposed, it would just be a matter of adding a separate handler to use ACP's schema), you'd be able to interact with Jean's sessions from Android/iOS apps, Slack, Telegram, Discord, and more.
Hi, I've been playing around with the code, implementing custom features for a private fork, and seeing how many edge cases and ad-hoc fixes for the different backends there are, made me think that it's crazy that this was built almost entirely by a single person... huge shoutout @andrasbacsai :).
Anyway, a few days ago Agent Client Protocol (ACP, by Zed and JetBrains) announced support for effort modes. Until then, having Jean interface with the CLIs was the only way to take advantage of all the features, and all those edge cases and workarounds were inevitable.
However, now that ACP supports all the needed features, I wanna propose moving away from that architecture and using ACP instead. This would entirely remove all the backend-specific implementations. Every available option will be exposed by the SDK with a single standard for a bunch of different backends (including Claude, Codex, Cursor, and ~30 other providers).
Adding support for a new backend is just a matter of adding 3 lines of code to a single file:
pub enum AcpProvider { + Claude, } pub fn adapter_argv(self) -> &'static [&'static str] { match self { + AcpProvider::Claude => &["npx", "--yes", "@agentclientprotocol/claude-agent-acp@0.31.0"], } } pub fn as_str(self) -> &'static str { match self { + AcpProvider::Claude => "claude-acp", } }No other changes whatsoever.
Whenever a new Claude model is released, or some new feature is implemented (e.g., effort level for one model is different than effort level of another model), all you need to do is update the package version, which will pick up the update:
pub fn adapter_argv(self) -> &'static [&'static str] { match self { "npx", "--registry", "https://registry.npmjs.org", "--yes", - "@agentclientprotocol/claude-agent-acp@0.30.0", + "@agentclientprotocol/claude-agent-acp@0.31.0", } }No other changes in your code. Neither backend nor frontend.
You can even use the ACP Registry directly, which automatically exposes all the available agents, so you don’t even need to do anything (not even releasing a new version of the app) when a new agent version is released or a new provider is onboarded to ACP.
In theory, that
providers.rsfile could be the only place where "claude" is referenced in the whole repository (other than `CLAUDE.md :) ). In practice I think it still makes sense to have provider-specific logic, for example the preferences page with the Claude skills (available only for Claude), but that doesn't affect the logic of the rest of the code in any way.I started looking into this after noticing some issues with the permission flow. Implementing a fix for one thing broke another, and I felt like I was adding too many fixes/workarounds for things that someone else should fix or implement the workarounds for (which is exactly ACP).
The main cons are:
I tried to integrate ACP in Jean and I'd say it's extremely stable, which TBH is the thing I'm interested in the most. All the other features are extremely useful and nice to have, but I think having a very stable core chat experience should be the highest priority, and now that effort mode is available I think that makes it compatible with all Jean's already-available features (except for the AskUserQuestion UI which, TBH, it's nice to have but not a dealbreaker).
IMHO the pros outweigh the cons.
A proof-of-concept is available here if you want to try it out: #337. I you think this might be helpful, I'm happy to split this huge PR into smaller, well-tested, ones.
Also BTW, ACP is not just a standard to allow interfacing editors (Jean) with agents, but it also allows to interface agents with clients. For example, after making some changes to the code to expose Jean's APIs (which are already exposed, it would just be a matter of adding a separate handler to use ACP's schema), you'd be able to interact with Jean's sessions from Android/iOS apps, Slack, Telegram, Discord, and more.