Skip to content

feat(cli): add config init and config show subcommands#1203

Merged
geoffjay merged 16 commits into
issue-1199from
issue-1195
May 13, 2026
Merged

feat(cli): add config init and config show subcommands#1203
geoffjay merged 16 commits into
issue-1199from
issue-1195

Conversation

@geoffjay
Copy link
Copy Markdown
Owner

Adds agent config init and agent config show subcommands to the CLI, backed by agentd_common::config::load().

  • config init writes a fully-commented config.toml template to the XDG config path; refuses to overwrite without --force
  • config show prints the merged config (defaults + file + env vars) as TOML or --json; --raw prints the on-disk file only
  • 4 unit tests: template TOML validity, struct deserialisation, overwrite guard logic

Closes #1195

@geoffjay geoffjay added the review-agent Used to invoke a review by an agent tracking this label label May 12, 2026
Copy link
Copy Markdown
Owner Author

@geoffjay geoffjay left a comment

Choose a reason for hiding this comment

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

Review: feat(cli): add config init and config show subcommands

Stack position: This PR (issue-1195) is stacked on PR #1202 (issue-1199) which is stacked on PR #1201 (issue-1194, needs-rework). It cannot merge until #1201 is fixed and the chain restacks. After #1201's rework lands, the template and any tests that reference field names (e.g. AGENTD_HISTORY_SIZE) will need updating.

The implementation itself is solid and well-structured. One test-quality issue needs a fix before this merges.


Blocking

Overwrite-guard tests don't call cmd_init — they test a boolean expression

#[test]
fn init_refuses_overwrite_without_force() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("config.toml");
    std::fs::write(&path, "# existing").unwrap();

    // Simulate the guard logic (path.exists() && !force)
    assert!(path.exists());
    let would_error = path.exists() && !false;  // always true
    assert!(would_error, "should refuse overwrite without --force");
}

cmd_init is never called. The test writes a file, then asserts that true && true == true. It would pass whether cmd_init bails, silently overwrites, or panics. The same holds for init_allows_overwrite_with_forcepath.exists() && !true is false by definition.

The fix is to test the actual function. Since cmd_init is private, expose it as pub(crate) and use AGENTD_CONFIG to redirect it at the tempdir:

#[test]
fn init_refuses_overwrite_without_force() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("config.toml");
    std::fs::write(&path, "# existing").unwrap();
    std::env::set_var("AGENTD_CONFIG", path.to_str().unwrap());

    let result = cmd_init(false);
    std::env::remove_var("AGENTD_CONFIG");

    assert!(result.is_err());
    let msg = result.unwrap_err().to_string();
    assert!(msg.contains("--force"), "error should mention --force, got: {msg}");
}

#[test]
fn init_allows_overwrite_with_force() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("config.toml");
    std::fs::write(&path, "# existing").unwrap();
    std::env::set_var("AGENTD_CONFIG", path.to_str().unwrap());

    let result = cmd_init(true);
    std::env::remove_var("AGENTD_CONFIG");

    assert!(result.is_ok());
    let written = std::fs::read_to_string(&path).unwrap();
    assert!(written.contains("[general]"), "template should have been written");
}

(If env-var mutation in tests needs serialisation, add an ENV_LOCK-style mutex as in agentd-common's config tests.)


Non-blocking observations

1. config_file_path() called twice in cmd_show's None arm

None => {
    eprintln!("{}", "No config file found.".yellow());
    eprintln!(
        "Run `agent config init` to create one at {}",
        config_file_path()          // ← second XDG lookup
            .map(|p| p.display().to_string())
            .unwrap_or_else(|| "~/.config/agentd/config.toml".to_string())
    );
}

path (the first lookup) is already in scope. Both calls resolve the same value. Replace the second call with path.as_ref().map(|p| p.display().to_string()).unwrap_or_else(...).

2. config_file_path() local wrapper is unnecessary

fn config_file_path() -> Option<PathBuf> {
    agentd_common::config::config_file_path()
}

Two call sites in the file could simply call agentd_common::config::config_file_path() directly, or add use agentd_common::config::config_file_path; at the top. The wrapper adds a layer of indirection for no gain.

3. Template will need updating after #1201's rework

The template documents AGENTD_HISTORY_SIZE for hook.history_size. If #1201's rework resolves the AGENTD_HISTORY_SIZE collision (hook vs monitor) by renaming to AGENTD_HOOK_HISTORY_SIZE, this comment and any new monitor fields need to be reflected here. Note it in the PR for whoever does the restack.

4. TOCTOU in cmd_init — the path.exists() check and fs::write are not atomic. Acceptable for a config-init command; no action needed.


What's correct

  • template_is_valid_toml and template_deserialises_to_agentd_config are meaningful regressions tests — good
  • All template field values match the AgentdConfig compiled defaults ✓
  • execute() is sync (correct — no async I/O; consistent with auth command pattern) ✓
  • anyhow::Context used on both Option (via the ? in cmd_init) and Result (serialization) — correct ✓
  • Error messages are user-friendly and actionable ✓
  • colored::Colorize for the success line — consistent with existing CLI output ✓
  • toml = { workspace = true } and agentd-common = { path = "../common" } — dependency additions are correct ✓
  • Config dispatch in main.rs is synchronous (command.execute(cli.json)? without .await) — correct ✓

Summary: Fix the overwrite-guard tests to call cmd_init rather than evaluate boolean literals. Everything else is clean. Hold for parent #1201 restack.

@geoffjay geoffjay added needs-rework PR has review feedback that must be addressed before merging and removed review-agent Used to invoke a review by an agent tracking this label labels May 12, 2026
@geoffjay geoffjay linked an issue May 12, 2026 that may be closed by this pull request
9 tasks
@geoffjay geoffjay added review-agent Used to invoke a review by an agent tracking this label and removed needs-rework PR has review feedback that must be addressed before merging labels May 12, 2026
geoffjay and others added 12 commits May 12, 2026 16:29
…_file_path indirection

- Make cmd_init pub(crate) so tests can call it directly
- Replace boolean-literal overwrite-guard tests with real tests that set
  AGENTD_CONFIG to a tempdir and call cmd_init; verify error message mentions
  --force on refusal and that the template is written on force-overwrite
- Add ENV_LOCK mutex to serialise tests that mutate AGENTD_CONFIG
- Remove the config_file_path() local wrapper; import via use instead
- Fix cmd_show None arm to reuse the already-resolved path rather than
  calling config_file_path() a second time

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g tests

Add ENV_LOCK to mcp and index config test modules so env-var-mutating
tests are serialised in the parallel test harness. Also clear AGENTD_PORT
(set by agentd at runtime) before asserting default port values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s; add TODO(#1201) comments

Replace silent unwrap_or_default() with unwrap_or_else(|e| tracing::warn\!(...)) in
all six migrated load() callers (hook, monitor, mcp, memory::EmbeddingConfig,
memory::LanceConfig, index, ui). Operators now see a warning when config.toml is
malformed rather than silently falling back to compiled defaults.

Add TODO(#1201) comments on every hardcoded fallback that is pending a schema
addition: notify_on_failure, notify_on_long_running, long_running_threshold_ms
(hook); cpu/memory/disk_alert_threshold, history_size (monitor);
embedding_endpoint (index.embedding); lance_table (index.lance).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a ValidateConfig trait to agentd_common with implementations for all
12 shared config sections. Add AgentdConfig::validate() that collects
errors from every section. Implement ValidateConfig on all service-level
config structs and wire validate() into each service's startup path so
misconfigurations fail early with descriptive error messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create dedicated config.rs modules for core, orchestrator, wrap, ask,
notify, and communicate. Each struct loads from agentd_common::config::load()
first, then overlays legacy AGENTD_* env vars for backward compatibility.
All inline env::var() calls in main.rs replaced with config struct access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add impl ValidateConfig for EmbeddingConfig and LanceConfig in
  crates/memory/src/config.rs (blocking gap: memory was the only service
  with no validation at startup)
- Wire validation in memory/main.rs: call validate() on embedding and
  lance configs, load shared config for port/host so port=0 is caught
  before any I/O with a clear error message
- Remove validate_inner() indirection in crates/index/src/config.rs;
  move logic directly into impl ValidateConfig for IndexConfig and
  import the trait in index/main.rs and the test module
- Make validate_url() pub in agentd-common so service crates can reuse
  it without duplicating the http/https check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ilures

Replace silent unwrap_or_default() with unwrap_or_else(|e| { tracing::warn\!(...) })
in all six new service config loaders (ask, communicate, core, notify, orchestrator,
wrap). Restore diagnostic warn\!() calls for invalid AGENTD_WRAP_HISTORY_BYTES and
AGENTD_WRAP_CHANNEL_CAPACITY env vars with TODO comments referencing #1201.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create docs/configuration.md with quick start, file location, precedence
  rules, complete key reference table, per-service sections, migration guide,
  and four example configs (minimal, development, production, Docker)
- Fix the env var table in agentd-common/config.rs to include all overrides
  (AGENTD_INDEX_LANGUAGES, AGENTD_RECONCILE_INTERVAL_SECS, all MCP URLs,
  AGENTD_NOTIFY_SERVICE_URL, AGENTD_COLLECTION_INTERVAL_SECS)
- Add link to configuration reference from README.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
geoffjay added 2 commits May 12, 2026 16:59
docs: add configuration system documentation
feat: add ValidateConfig trait and per-service validation
geoffjay added 2 commits May 12, 2026 17:00
feat: add config structs for services with inline env var config
refactor: migrate services with existing config.rs to shared TOML config
@geoffjay geoffjay merged commit 993d51e into issue-1199 May 13, 2026
@geoffjay geoffjay deleted the issue-1195 branch May 13, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-agent Used to invoke a review by an agent tracking this label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add agent config init and config show CLI subcommands

1 participant