From 24a0b6437df0d8bf4887b147a36cf89f8e4187ad Mon Sep 17 00:00:00 2001 From: ColonistOne Date: Tue, 7 Apr 2026 18:46:53 +0100 Subject: [PATCH] Expand config validation with warnings for common issues Validation now catches more errors upfront: - Empty llm.base_url or llm.model - Empty identity.name or identity.colonies - Unwritable state_file or memory_file paths New warnings() method flags non-fatal issues: - Empty interests (no topic context for LLM) - Default bio/personality (generic agent voice) - Very low max_memory_messages (quick context loss) Warnings logged at startup during colony-agent run. Co-Authored-By: Claude Opus 4.6 (1M context) --- colony_agent/cli.py | 3 +++ colony_agent/config.py | 46 ++++++++++++++++++++++++++++++++++++ tests/test_config.py | 53 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/colony_agent/cli.py b/colony_agent/cli.py index db25267..67665e9 100644 --- a/colony_agent/cli.py +++ b/colony_agent/cli.py @@ -186,6 +186,9 @@ def cmd_run(args: argparse.Namespace) -> None: print(f"Config error: {e}") sys.exit(1) + for w in config.warnings(): + logging.getLogger("colony-agent").warning("Config: %s", w) + agent = ColonyAgent(config, dry_run=getattr(args, 'dry_run', False)) if args.once: agent.run_once() diff --git a/colony_agent/config.py b/colony_agent/config.py index 548584c..eb71ad2 100644 --- a/colony_agent/config.py +++ b/colony_agent/config.py @@ -110,4 +110,50 @@ def validate(self) -> list[str]: errors.append(f"llm.provider must be 'openai-compatible', got '{self.llm.provider}'") if self.behavior.heartbeat_interval < 60: errors.append("heartbeat_interval must be at least 60 seconds") + if not self.llm.base_url: + errors.append("llm.base_url is required") + if not self.llm.model: + errors.append("llm.model is required") + if not self.identity.name: + errors.append("identity.name is required") + if not self.identity.colonies: + errors.append("identity.colonies must contain at least one colony") + + # Check file paths are writable + for label, path in [("state_file", self.state_file), ("memory_file", self.memory_file)]: + p = Path(path) + parent = p.parent + if p.exists() and not os.access(p, os.W_OK): + errors.append(f"{label} is not writable: {path}") + elif not p.exists() and parent.exists() and not os.access(parent, os.W_OK): + errors.append(f"{label} directory is not writable: {parent}") + return errors + + def warnings(self) -> list[str]: + """Return non-fatal warnings about the configuration.""" + warns = [] + if not self.identity.interests: + warns.append( + "identity.interests is empty — the agent won't have " + "topic context for LLM decisions" + ) + if not self.identity.bio or self.identity.bio == "An AI agent on The Colony.": + warns.append( + "identity.bio is generic — a specific bio helps the LLM " + "generate better introductions and comments" + ) + if ( + not self.identity.personality + or self.identity.personality == "Friendly, curious, and helpful." + ): + warns.append( + "identity.personality is the default — customizing it " + "gives the agent a more distinctive voice" + ) + if self.max_memory_messages < 20: + warns.append( + f"max_memory_messages is very low ({self.max_memory_messages}) " + f"— the agent will forget context quickly" + ) + return warns diff --git a/tests/test_config.py b/tests/test_config.py index 56c1bd6..0c7850b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -165,7 +165,60 @@ def test_validate_low_heartbeat(self): errors = config.validate() assert any("heartbeat_interval" in e for e in errors) + def test_validate_empty_base_url(self): + config = AgentConfig(api_key="col_x", llm=LLMConfig(base_url="")) + errors = config.validate() + assert any("base_url" in e for e in errors) + + def test_validate_empty_model(self): + config = AgentConfig(api_key="col_x", llm=LLMConfig(model="")) + errors = config.validate() + assert any("model" in e for e in errors) + + def test_validate_empty_name(self): + config = AgentConfig(api_key="col_x", identity=IdentityConfig(name="")) + errors = config.validate() + assert any("name" in e for e in errors) + + def test_validate_empty_colonies(self): + config = AgentConfig(api_key="col_x", identity=IdentityConfig(colonies=[])) + errors = config.validate() + assert any("colonies" in e for e in errors) + def test_validate_valid(self): config = AgentConfig(api_key="col_test") errors = config.validate() assert errors == [] + + +class TestWarnings: + def test_no_warnings_with_customized_config(self): + config = AgentConfig( + api_key="col_x", + identity=IdentityConfig( + bio="I research distributed systems.", + personality="Technical and thorough.", + interests=["CRDTs", "consensus"], + ), + ) + assert config.warnings() == [] + + def test_warns_empty_interests(self): + config = AgentConfig(api_key="col_x", identity=IdentityConfig(interests=[])) + warns = config.warnings() + assert any("interests" in w for w in warns) + + def test_warns_default_bio(self): + config = AgentConfig(api_key="col_x") + warns = config.warnings() + assert any("bio" in w for w in warns) + + def test_warns_default_personality(self): + config = AgentConfig(api_key="col_x") + warns = config.warnings() + assert any("personality" in w for w in warns) + + def test_warns_low_memory(self): + config = AgentConfig(api_key="col_x", max_memory_messages=5) + warns = config.warnings() + assert any("memory" in w.lower() for w in warns)