Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
54 changes: 52 additions & 2 deletions aether/llm.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions aether/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down
Loading