diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d791b5..e701249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to Aether will be documented in this file. +## Unreleased + +### Added — Sprint A Phase 2 (Provider seams) +- **`build_llm_client(profile=...)`** in `aether/llm.py`: soft-imports Core `get_profile` for model/host/timeout defaults when Coastal-Alpine-Core ≥0.5.8 is installed; falls back to Aether defaults when Core is absent +- **`llm_profile`** constructor arg on `AetherOrchestrator` + env `AETHER_LLM_PROFILE` +- Chat transport remains Aether `OllamaClient` (`/api/chat` + multimodal images) — Core generate API is not forced onto the ReAct path +- CAT stamp · local-first · no hard dependency on Core + +### Added — Sprint A Phase 1 (SessionEvent) +- **`aether/session_audit.py`**: soft-import bridge to Coastal-Alpine-Core `SessionEventStore` + - Null fallback when Core is not installed (zero behaviour change) + - Hooks on `start_task`, `call_tool`, `_execute_skill`, `_approve_if_needed`, `run_react_loop` / `run_pipeline` + - Emits: `session_start`, `prompt_received`, `tool_call`, `tool_result`, `skill_applied`, + `approval_required` / `approval_granted` / `approval_denied`, `session_end`, `error` + - Local-first JSONL (`~/.aether/session_events.jsonl`), no secrets in payloads +- Optional constructor flags: `enable_session_events=True`, `session_event_path=…`, `tenant_id=…` +- CAT stamp · Te Mana Raraunga evidence path · HITL-ready audit only + ## [0.7.1] - 2026-07-27 ### Added — Alignment Week @@ -40,18 +58,6 @@ All notable changes to Aether will be documented in this file. ### Notes External claims of compliance (grants, investor materials, pilot agreements) require explicit founder approval via this skill. -## Unreleased - -### Added — Sprint A Phase 1 (SessionEvent) -- **`aether/session_audit.py`**: soft-import bridge to Coastal-Alpine-Core `SessionEventStore` - - Null fallback when Core is not installed (zero behaviour change) - - Hooks on `start_task`, `call_tool`, `_execute_skill`, `_approve_if_needed`, `run_react_loop` / `run_pipeline` - - Emits: `session_start`, `prompt_received`, `tool_call`, `tool_result`, `skill_applied`, - `approval_required` / `approval_granted` / `approval_denied`, `session_end`, `error` - - Local-first JSONL (`~/.aether/session_events.jsonl`), no secrets in payloads -- Optional constructor flags: `enable_session_events=True`, `session_event_path=…`, `tenant_id=…` -- CAT stamp · Te Mana Raraunga evidence path · HITL-ready audit only - ### Super Grok skills structure (2026-07-19) - **`grants-agent` v0.1.0** - structured from Super Grok chat *Maori AI startups - Grants and Funding* - Discover / fit-score / draft / track / Kotahitanga modes diff --git a/aether/llm.py b/aether/llm.py index 60c35c1..cb7f26f 100644 --- a/aether/llm.py +++ b/aether/llm.py @@ -1,12 +1,16 @@ """ -Aether LLM Module — Ollama integration (Phase C) +Aether LLM Module — Ollama integration (Phase C) + Sprint A Phase 2 provider seam Design goals: -- stdlib only (urllib), no new dependencies — suits edge/sovereign deployment +- stdlib only (urllib), no hard dependency on Core — suits edge/sovereign deployment +- Soft-import Core ProviderProfile / get_provider for model/host defaults when present +- Chat transport remains Aether OllamaClient (/api/chat + multimodal images) - Graceful degradation: if Ollama is unreachable, orchestrator falls back to the deterministic pipeline - Strict JSON action contract with validation against the tool/skill registry - Injectable transport for offline unit testing + +CAT: local-first, http(s) only, no secrets in profiles. """ import json @@ -44,6 +48,52 @@ def _require_http_url(url: str) -> str: return cleaned +def _resolve_from_core_profile( + profile: Optional[str], +) -> Dict[str, Any]: + """Soft-import Core ProviderProfile. Returns empty dict when Core absent.""" + if not profile: + return {} + try: + from coastal_alpine_core import get_profile # type: ignore + + p = get_profile(profile) + return { + "base_url": getattr(p, "base_url", None), + "model": getattr(p, "model", None), + "timeout": int(getattr(p, "timeout", 60) or 60), + "max_retries": int(getattr(p, "max_retries", 2) or 2), + } + except Exception as e: + logger.debug("Core get_profile(%s) unavailable: %s", profile, e) + return {} + + +def build_llm_client( + *, + profile: Optional[str] = None, + base_url: Optional[str] = None, + model: Optional[str] = None, + timeout: Optional[int] = None, + max_retries: Optional[int] = None, + transport: Optional[Callable[[str, Dict[str, Any], int], str]] = None, +) -> "OllamaClient": + """ + Construct an OllamaClient, optionally resolving defaults from a Core + ProviderProfile (Sprint A Phase 2 soft seam). + + Explicit kwargs always win over profile fields over module defaults. + """ + resolved = _resolve_from_core_profile(profile) + return OllamaClient( + base_url=base_url or resolved.get("base_url") or DEFAULT_BASE_URL, + model=model or resolved.get("model") or DEFAULT_MODEL, + timeout=timeout if timeout is not None else resolved.get("timeout", 60), + max_retries=max_retries if max_retries is not None else resolved.get("max_retries", 2), + transport=transport, + ) + + @dataclass class LLMDecision: thought: str diff --git a/aether/orchestrator.py b/aether/orchestrator.py index c899597..9c1b9b9 100644 --- a/aether/orchestrator.py +++ b/aether/orchestrator.py @@ -10,6 +10,7 @@ - Skills directory resolved robustly (CWD -> env -> ~/.aether/skills) Sprint A Phase 1: optional SessionEvent audit via soft-import of Core. +Sprint A Phase 2: optional llm_profile resolves model/host from Core ProviderProfile. """ import logging @@ -24,7 +25,7 @@ from .tools import ToolRegistry, ToolExecutor, ToolCache from .tools.base import ToolResult from .skills.loader import SkillLoader -from .llm import OllamaClient, build_react_messages, parse_decision +from .llm import OllamaClient, build_llm_client, build_react_messages, parse_decision # Optional SessionEvent audit (Sprint A Phase 1) — soft dependency try: @@ -82,7 +83,8 @@ def __init__(self, memory_path: Optional[str] = None, skills_directory: Optional enable_computer_use: bool = False, enable_session_events: bool = True, session_event_path: Optional[str] = None, - tenant_id: Optional[str] = None): + tenant_id: Optional[str] = None, + llm_profile: Optional[str] = None): self.state: Optional[TaskState] = None self.errors: List[str] = [] self.auto_remediate: bool = False @@ -92,7 +94,12 @@ def __init__(self, memory_path: Optional[str] = None, skills_directory: Optional self.memory = AetherMemory(persist_path=memory_path) self.guardrails = Guardrails() self.threat_modeler = ThreatModeler() - self.llm = llm or OllamaClient() + # Sprint A Phase 2: optional Core profile for model/host defaults + if llm is not None: + self.llm = llm + else: + profile = llm_profile or os.environ.get("AETHER_LLM_PROFILE") + self.llm = build_llm_client(profile=profile) self.use_llm = use_llm self.enable_computer_use = bool(enable_computer_use)