git-ents.gitmain
⌘K
foforge
commit 91a009c
Revert the agent-facing additions, keep the review work

Back out the two agent-feature commits — the auto/manual review-policy descriptions (e3bf42f) and the forge-wide agent provider + default model config (82769e7) — since we’re not expanding the agent-sessions feature yet. The review withdraw state (fc942bc) and the issue-shaped review detail page (a151c6e) stay; they’re a separate, wanted improvement.

The reverted work remains in history (merged at 680d07c, and on the worktree-agent-sessions branch) to restore later by reverting this commit.

This reverts commit 82769e7ec and commit e3bf42f.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Joseph D. Carpinelli · 28 days ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/cli/git-ents/src/cli.rs @@ -102,17 +102,6 @@ #[facet(args::subcommand)] action: AccountAction, }, - /// Manage this repository's forge-wide configuration at - /// `refs/meta/config`: today, non-secret agent-runtime defaults (a - /// provider name, a default model id) alongside the gate's own - /// `epoch`/`workers` fields. The API token is never here; it lives in - /// the deployment-time credential seam - /// (`GIT_ENTS_CREDENTIALS_FILE`), never in a signed, replicated tree. - Config { - /// The config action to run. - #[facet(args::subcommand)] - action: ConfigAction, - }, /// Manage the configured effects at `refs/meta/effects/<name>` and run /// them locally. Effect { @@ -328,32 +317,6 @@ }, } -/// `git ents config` actions. -#[derive(Facet)] -#[repr(u8)] -pub enum ConfigAction { - /// Show this repository's current forge-wide configuration. - Show, - /// Narrow the agent-runtime defaults. Each flag is independent: omit - /// one to leave whatever it currently holds untouched rather than - /// clearing it. - Set { - /// The agent runtime's provider name (e.g. `anthropic`); - /// non-secret -- the API token itself is never a flag here, only - /// `git-ents`'s own `GIT_ENTS_CREDENTIALS_FILE` seam. - #[facet(args::named)] - agent_provider: Option<String>, - /// The agent runtime's default model id (e.g. - /// `claude-sonnet-5`), used whenever a session start omits its - /// own `--model`. - #[facet(args::named)] - agent_default_model: Option<String>, - /// Key to sign with; defaults to `user.signingkey`. - #[facet(args::named)] - key: Option<PathBuf>, - }, -} - /// `git ents effect` actions. #[derive(Facet)] #[repr(u8)]
crates/cli/git-ents/src/exe.rs @@ -8,8 +8,8 @@ )] use crate::cli::{ - AccountAction, AgentAction, Cli, CommentAction, ConfigAction, EffectAction, HookAction, - InboxAction, IssueAction, MembersAction, RedactAction, ReviewAction, ToolchainAction, Top, + AccountAction, AgentAction, Cli, CommentAction, EffectAction, HookAction, InboxAction, + IssueAction, MembersAction, RedactAction, ReviewAction, ToolchainAction, Top, }; use crate::commands; use crate::error::Result; @@ -69,7 +69,6 @@ } Top::Members { action } => run_members(action, out), Top::Account { action } => run_account(action, out), - Top::Config { action } => run_config(action, out), Top::Effect { action } => run_effect(action, out), Top::Toolchain { action } => run_toolchain(action, out), Top::Comment { action } => run_comment(action, out), @@ -173,34 +172,6 @@ Ok(()) } -fn run_config(action: ConfigAction, out: &mut impl std::io::Write) -> Result<()> { - let root = LocalRoot::discover(".")?; - match action { - ConfigAction::Show => { - let config = commands::config::show(&root)?; - let _ = writeln!( - out, - "agent_provider: {}", - config.agent_provider.as_deref().unwrap_or("(unset)") - ); - let _ = writeln!( - out, - "agent_default_model: {}", - config.agent_default_model.as_deref().unwrap_or("(unset)") - ); - } - ConfigAction::Set { - agent_provider, - agent_default_model, - key, - } => { - commands::config::set(&root, agent_provider, agent_default_model, key)?; - let _ = writeln!(out, "config updated"); - } - } - Ok(()) -} - fn run_effect(action: EffectAction, out: &mut impl std::io::Write) -> Result<()> { let root = LocalRoot::discover(".")?; match action {
crates/kernel/ents-gate/src/config.rs @@ -23,14 +23,11 @@ /// /// `model.sdoc` defines no Config entity yet, so this struct is the /// first (and currently only) definition of the config tree's shape; it -/// lives here rather than in `ents-model` because these were, until now, -/// the only fields any crate reads. `agent_provider`/`agent_default_model` -/// (below) are the first fields this crate itself has no use for — they -/// exist purely for other crates (`ents-web`'s new-session form) to read -/// — but they stay here rather than moving to `ents-model` today: that -/// move is still deferred until configuration grows enough non-gate -/// fields to justify the storage migration (`meta-ref.migration`) in one -/// pass, rather than one field at a time. +/// lives here rather than in `ents-model` because these are the only +/// fields any crate reads today. When configuration grows non-gate fields +/// (description, role rules, ...), the entity moves to `ents-model` and +/// that change is a storage migration like any other struct change +/// (`meta-ref.migration`). /// /// `epoch` is `None` on a config written before verification was turned /// on. Once it is `Some`, the gate applies the tip invariant to every @@ -64,21 +61,6 @@ /// narrowing lands. Empty by default: no worker is designated until a /// signed config write adds one. pub workers: Vec<MemberId>, - /// The agent runtime's provider name (e.g. `"anthropic"`), forge-wide; - /// `None` until a signed config write sets one. Non-secret by - /// construction — the credential that authenticates to the provider - /// never lives here or anywhere in `refs/meta/*`, only in the - /// deployment-time seam (`git-ents`'s `credentials.rs`, - /// `GIT_ENTS_CREDENTIALS_FILE`). The agent runtime that would consume - /// this is not built yet; storing and reading it is this field's - /// entire job for now. - pub agent_provider: Option<String>, - /// The agent runtime's default model id (e.g. `"claude-sonnet-5"`), - /// forge-wide; `None` until a signed config write sets one. Read as a - /// fallback ahead of any hardcoded default a caller carries — today, - /// `ents-web`'s new-agent-session form falls back to it before its own - /// compiled-in constant. - pub agent_default_model: Option<String>, } /// The config recorded by the tree of the commit at `oid`, or an @@ -137,21 +119,3 @@ ) -> Result<Vec<MemberId>> { Ok(current_config(refs, objects)?.workers) } - -/// The forge-wide agent provider name currently in force, read from -/// `refs/meta/config`'s tip; `None` when the config ref does not exist or -/// names no provider. Public (unlike [`designated_workers`], which only -/// this crate's gate consults) because the only consumer today, an agent -/// runtime, does not live in this crate. -pub fn agent_provider(refs: &dyn RefStoreRead, objects: &dyn Find) -> Result<Option<String>> { - Ok(current_config(refs, objects)?.agent_provider) -} - -/// The forge-wide default agent model id currently in force, read from -/// `refs/meta/config`'s tip; `None` when the config ref does not exist or -/// names no default — callers fall back to their own compiled-in default -/// (`ents-web`'s new-agent-session form, today) rather than treating -/// `None` as an error. -pub fn agent_default_model(refs: &dyn RefStoreRead, objects: &dyn Find) -> Result<Option<String>> { - Ok(current_config(refs, objects)?.agent_default_model) -}
crates/kernel/ents-gate/src/lib.rs @@ -155,7 +155,7 @@ mod verdict; mod verify; -pub use config::{Config, agent_default_model, agent_provider}; +pub use config::Config; pub use error::{Error, Result}; pub use verdict::{Admission, AdmissionKind, Refusal, Requirement, Verdict}; pub use verify::{Update, verify};
crates/kernel/ents-gate/tests/gate.rs @@ -856,7 +856,6 @@ &Config { epoch: Some(200), workers: vec![MemberId::new(worker_id)], - ..Config::default() }, Some(&f.admin), seconds,
crates/cli/ents-web/src/assets/ents.css @@ -572,7 +572,6 @@ } .picker .opt .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-4); } .picker .opt.active { font-weight: 600; } -.opt-help { font-size: 12px; line-height: 1.5; margin: 8px 0 0; } /* Label picker chips (`labelPicker`): pick existing or type-and-create. */ .label-chip {
crates/cli/ents-web/src/pages/agents.rs @@ -61,7 +61,6 @@ .map(|entry| (entry.refname, entry.error)) .collect(); let default_base = default_base_ref(&state); - let default_model = resolved_default_model(&state); Ok(super::layout_split( &super::RepoHeader::from_state(&state), &super::identity_label(&state), @@ -80,7 +79,7 @@ } div.card { div.card-header { "Start an Agent Session" } - (new_form(&session, &default_base, &default_model)) + (new_form(&session, &default_base)) } } }, @@ -697,19 +696,18 @@ /// The start-a-session form (`POST /agents`, `docs/agent-sessions-plan.adoc`'s /// Phase 3, "mobile-critical"): a prompt textarea, a base-branch text input /// pre-filled with `default_base` ([`default_base_ref`]), a model text -/// input pre-filled with `default_model` ([`resolved_default_model`]), and -/// a closed two-option review policy picker defaulting to `manual` -/// (mirrors `crate::pages::commits::start_review_form`'s identical -/// closed-verdict picker) -- deliberately no toolchain or retry field: the -/// plan's own words are "complexity lives in the session doc, not the -/// form." -fn new_form(session: &Session, default_base: &str, default_model: &str) -> Markup { +/// input pre-filled with [`DEFAULT_MODEL`], and a closed two-option review +/// policy picker defaulting to `manual` (mirrors +/// `crate::pages::commits::start_review_form`'s identical closed-verdict +/// picker) -- deliberately no toolchain or retry field: the plan's own +/// words are "complexity lives in the session doc, not the form." +fn new_form(session: &Session, default_base: &str) -> Markup { html! { form method="post" action="/agents" { (super::csrf_input(session)) label { "prompt" textarea name="prompt" {} } label { "base branch" input type="text" name="base_ref" value=(default_base); } - label { "model" input type="text" name="model" value=(default_model); } + label { "model" input type="text" name="model" value=(DEFAULT_MODEL); } div { p.muted { "review policy" } div.picker { @@ -724,12 +722,6 @@ "auto" } } - p.opt-help.muted { - strong { "Manual" } - " — no review opens on its own; you start one yourself when you want it. " - strong { "Auto" } - " — a review of the result opens automatically once the run finishes." - } } div.composer-buttons { a.composer-cancel href="/agents" { "Cancel" } @@ -739,26 +731,11 @@ } } -/// The model id [`resolved_default_model`] falls back to when -/// `refs/meta/config` names no `agent_default_model` (or does not exist -/// yet) -- the same default id this codebase's own fixtures and -/// `git-ents::agent_worker` tests already use. +/// The model id [`new_form`] pre-fills and [`NewForm::model`] defaults to +/// when a submission omits the field entirely -- the same default id this +/// codebase's own fixtures and `git-ents::agent_worker` tests already use. const DEFAULT_MODEL: &str = "claude-sonnet-5"; -/// The model id [`new_form`] pre-fills and [`NewForm::model`] falls back to -/// when a submission omits the field entirely: `refs/meta/config`'s -/// `agent_default_model` (`ents_gate::agent_default_model`) when a signed -/// config write has set one, else [`DEFAULT_MODEL`] -- a forge-wide -/// default lets an operator change what every new session starts at -/// without touching this crate's own compiled-in fallback, which stays as -/// the floor for a repository that has never configured one. -fn resolved_default_model<O: Find>(state: &AppState<O>) -> String { - ents_gate::agent_default_model(state.refs.as_ref(), &*state.objects()) - .ok() - .flatten() - .unwrap_or_else(|| DEFAULT_MODEL.to_owned()) -} - /// The base ref [`new_form`] pre-fills: `refs/heads/<branch>` for the /// served repository's own current `HEAD` branch /// ([`super::RepoHeader::from_state`]), or plain `HEAD` when that cannot @@ -799,11 +776,10 @@ /// [`default_base_ref`]). #[serde(default = "default_base_ref_field")] base_ref: String, - /// The model id the run executes against; `None` when a submission - /// omits the field entirely, resolved by [`resolved_default_model`] - /// (see [`create`]). - #[serde(default)] - model: Option<String>, + /// The model id the run executes against; defaults to + /// [`DEFAULT_MODEL`]. + #[serde(default = "default_model_field")] + model: String, /// The session's initially resolved review policy: `auto` or `manual`; /// defaults to `manual` (see [`default_review_policy`]). #[serde(default = "default_review_policy")] @@ -812,6 +788,11 @@ csrf: String, } +/// [`NewForm::model`]'s serde default -- see [`DEFAULT_MODEL`]. +fn default_model_field() -> String { + DEFAULT_MODEL.to_owned() +} + /// `POST /agents`: start an agent session owned by the current signing /// identity's resolved member (`ents_forge::agent::new`), signed /// (`roots.web-signing`) on behalf of the current session @@ -837,11 +818,10 @@ super::require_csrf(&session, &form.csrf)?; let member = session_owner(&state); let identity = state.identity.as_ref(); - let model = form.model.unwrap_or_else(|| resolved_default_model(&state)); let new = NewAgentSession { member, prompt: form.prompt, - model, + model: form.model, toolchains: Vec::new(), base_ref: form.base_ref, review_policy: form
crates/cli/git-ents/src/commands/mod.rs @@ -12,7 +12,6 @@ pub mod agent; pub mod bootstrap; pub mod comment; -pub mod config; pub mod effect; pub mod inbox; pub mod issue;
crates/forge/ents-forge/src/agent/cli.rs @@ -33,9 +33,7 @@ /// The ref the run executes against as its starting point. #[facet(args::named, default = "HEAD")] base: String, - /// The session's initially resolved review policy. `manual` (default): - /// no review opens on its own; you start one yourself. `auto`: a review - /// of the result opens automatically once the run finishes. + /// The session's initially resolved review policy: auto or manual. #[facet(args::named, default = "manual")] review_policy: String, /// The genesis oid of a prior session this one retries.
crates/cli/git-ents/src/commands/config.rs @@ -1,96 +1,0 @@ -//! `git ents config`: forge-wide, non-secret agent-runtime defaults -//! (provider name, default model) recorded in `refs/meta/config` alongside -//! the gate's own `epoch`/`workers` fields (`ents_gate::Config`). -//! -//! The API token is deliberately not here and never will be: it lives only -//! in the deployment-time credential seam (`crate::credentials`, -//! `GIT_ENTS_CREDENTIALS_FILE`), never in a signed, replicated, -//! multi-reader tree. - -use ents_gate::Config; -use ents_model::namespace; -use ents_receive::{Identity, propose_entity}; -use gix_ref_store::RefStoreRead; - -use super::{actor, signer}; -use crate::error::Result; -use crate::mutate::outcome_to_result; -use crate::root::LocalRoot; - -/// `git ents config show`: this repository's current forge-wide -/// configuration -- [`Config::default`] (every field unset) when -/// `refs/meta/config` has no tip yet, the same "absent means unconfigured" -/// reading every `ents_gate::config` reader already gives. -/// -/// # Errors -/// -/// Propagates a ref-store or object read failure, or an unreadable config -/// tree. -pub fn show(root: &LocalRoot) -> Result<Config> { - #[expect( - clippy::expect_used, - clippy::unwrap_in_result, - reason = "CONFIG_REF is a fixed, compile-time-known-valid refname literal" - )] - let name: gix::refs::FullName = namespace::CONFIG_REF - .try_into() - .expect("fixed, valid refname"); - let Some(tip) = root.refs.get(name.as_ref())? else { - return Ok(Config::default()); - }; - let tree = super::commit_tree(&root.objects, tip)?; - Ok(facet_git_tree::deserialize::<Config>(&tree, &root.objects)?) -} - -/// `git ents config set`: narrow the agent-runtime defaults. Each argument -/// is an independent optional narrowing -- omit one to leave whatever it -/// currently holds untouched, rather than resetting it to `None`. Reads -/// the current config first and writes the merged whole back, so a `set` -/// naming only `agent_provider` cannot clobber an `agent_default_model` -/// set earlier, or the gate's own `epoch`/`workers`. -/// -/// # Errors -/// -/// Propagates a ref-store/object read failure or a signing failure; -/// otherwise see [`crate::mutate::outcome_to_result`] for how a reached -/// refusal renders. -pub fn set( - root: &LocalRoot, - agent_provider: Option<String>, - agent_default_model: Option<String>, - key: Option<std::path::PathBuf>, -) -> Result<()> { - let signer = signer(root, key)?; - let mut config = show(root)?; - if let Some(provider) = agent_provider { - config.agent_provider = Some(provider); - } - if let Some(model) = agent_default_model { - config.agent_default_model = Some(model); - } - #[expect( - clippy::expect_used, - clippy::unwrap_in_result, - reason = "CONFIG_REF is a fixed, compile-time-known-valid refname literal" - )] - let name: gix::refs::FullName = namespace::CONFIG_REF - .try_into() - .expect("fixed, valid refname"); - let identity = Identity { - actor: actor(&signer), - author: None, - sign: &|payload| signer.sign(payload), - }; - let outcome = propose_entity( - &root.refs, - &root.objects, - &root.events, - name, - &config, - &identity, - "Set agent config", - root.mode(), - )?; - outcome_to_result(outcome, None)?; - Ok(()) -}
crates/cli/git-ents/tests/config.rs @@ -1,65 +1,0 @@ -//! Integration coverage for `git ents config` against a real local -//! composition root (`roots.local`): narrowing the agent-runtime defaults -//! at `refs/meta/config` without disturbing fields `set` was not told to -//! touch, and reading an unconfigured repository back as -//! `ents_gate::Config::default()`. - -#![allow(clippy::expect_used, reason = "integration test")] - -mod common; - -use ents_gate::Config; -use git_ents::commands::config; -use git_ents::root::LocalRoot; - -/// A repository that has never had `refs/meta/config` written reads back -/// as every field unset -- the same "absent means unconfigured" reading -/// `ents_gate::config`'s own readers give, and what lets an old config -/// (predating `agent_provider`/`agent_default_model`) keep parsing. -// @relation(roots.local, scope=function, role=Verifies) -#[test] -fn absent_config_reads_as_default() { - let fixture = common::Fixture::new(1); - let root = LocalRoot::open(fixture.path()).expect("opens"); - - let config = config::show(&root).expect("shows"); - assert_eq!(config, Config::default()); -} - -/// `set` narrows one field at a time: a later call naming only -/// `agent_default_model` must not clobber an `agent_provider` an earlier -/// call set, and neither call ever touches `workers`/`epoch` (unset by -/// either call). -// @relation(roots.local, scope=function, role=Verifies) -#[test] -fn set_narrows_without_clobbering_other_fields() { - let fixture = common::Fixture::new(1); - let root = LocalRoot::open(fixture.path()).expect("opens"); - - config::set( - &root, - Some("anthropic".to_owned()), - None, - Some(fixture.key_path.clone()), - ) - .expect("sets provider"); - let config = config::show(&root).expect("shows"); - assert_eq!(config.agent_provider.as_deref(), Some("anthropic")); - assert_eq!(config.agent_default_model, None); - assert!(config.workers.is_empty()); - assert_eq!(config.epoch, None); - - config::set( - &root, - None, - Some("claude-sonnet-5".to_owned()), - Some(fixture.key_path.clone()), - ) - .expect("sets default model"); - let config = config::show(&root).expect("shows"); - assert_eq!(config.agent_provider.as_deref(), Some("anthropic")); - assert_eq!( - config.agent_default_model.as_deref(), - Some("claude-sonnet-5") - ); -}