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
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
# AURA wraps your loop — these variables are for integration examples
# (Ollama, OpenAI, Claude, Gemini, etc.). Core AURA does not require any of them.

# --- Local Ollama (recommended for dev / CI-free manual testing) ---
# --- Local Ollama (recommended for dev / Skillware+Ollama integration scripts) ---
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=llama3.2:1b

# --- Skillware integration scripts (integrations/skillware/) ---
# SKILLWARE_LIVE=1 # use real registry skills instead of MockSkill in reference_tool_host.py
# SKILLWARE_SKILL_PATH= # optional extra skill roots (see skillware skillware paths)

# --- Cloud model APIs (optional — pick what your integration uses) ---
# OPENAI_API_KEY=
# OPENAI_MODEL=gpt-4o-mini
Expand Down
27 changes: 22 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,35 @@ jobs:
pip install pip-audit
pip-audit
- name: black
run: black --check aura tests
run: black --check aura tests integrations examples
- name: flake8
run: flake8 aura tests
run: flake8 aura tests integrations examples
- name: pytest
run: pytest --cov=aura --cov-report=term-missing
run: pytest --cov=aura --cov-report=term-missing --ignore=tests/integration

skillware-live:
name: Skillware registry (live)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install with Skillware extra
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,skillware]"
- name: pytest skillware
run: pytest tests/test_skillware_integration.py -v

# Stable check name for branch protection (matrix cells are "Python 3.xx").
ci-ok:
name: lint-test
needs: lint-test
needs: [lint-test, skillware-live]
if: always()
runs-on: ubuntu-latest
steps:
- name: Confirm Python matrix succeeded
run: test "${{ needs.lint-test.result }}" = "success"
run: |
test "${{ needs.lint-test.result }}" = "success"
test "${{ needs.skillware-live.result }}" = "success"
2 changes: 1 addition & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
black --check aura tests
black --check aura tests integrations examples
flake8 aura tests
pytest --cov=aura --cov-report=term-missing

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ venv/
*.log
.aura/
!.aura/.gitkeep
.aura-example06-*/
AURA_PLAN.md
issues/
.mypy_cache/
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`aura paths`** — view paths; **`set-project`** and **`set-storage`** persist settings to YAML.
- **Interactive paths submenu** — replaces read-only home; agents menu adds **edit** wizard.
- **Splash polish** — blank line above ASCII logo; smoother Rich truecolor gradient on Windows Terminal.
- **`ToolHost` protocol** — host-agnostic contract in `aura.hosts`; `SkillwareHost` as reference adapter ([#22](https://github.com/ARPAHLS/aura/issues/22), [#12](https://github.com/ARPAHLS/aura/issues/12)).
- **Skill manifest merge at bind** — `MockSkill.manifest` / skill manifest merged into session rules; `skill.registered` spine event ([#32](https://github.com/ARPAHLS/aura/issues/32)).
- **Monitor observer preset** — profile `{ preset: monitor }` for after-call analytics; `observer.note` on spine ([#31](https://github.com/ARPAHLS/aura/issues/31)).
- **`integrations/skillware/`** — reference adapter index ([#19](https://github.com/ARPAHLS/aura/issues/19)).
- **Skillware registry loader** — `load_registry_skill`, `SkillwareHost.register_registry_skill`, `from_registry` ([#12](https://github.com/ARPAHLS/aura/issues/12)).
- **Integration scripts** — `reference_tool_host.py` (mock/live), `ollama_skill_loop.py` (Ollama + real skills).
- **Cloud body loops** — OpenAI, Anthropic, Gemini scripts under `integrations/` with provider READMEs.
- **Examples 05–06** — skill-type tour and sequencer skill chain (`SKILLWARE_LIVE=1` for registry skills).
- **Guide** — [docs/guides/aura-on-skillware.md](docs/guides/aura-on-skillware.md), follow-ups in [skillware-follow-ups.md](docs/guides/skillware-follow-ups.md).
- **`[integrations]` extra** — `skillware`, `ollama`, `openai`, `anthropic`, `google-generativeai` optional deps.

### Changed

Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Follow the [Code of Conduct](CODE_OF_CONDUCT.md). We welcome autonomous logical
### Style

- **No emojis** in source, docs, commits, or PR titles.
- **Black** (`black aura tests`) and **Flake8** (`flake8 aura tests`) — see [TESTING.md](docs/TESTING.md).
- **Black** (`black aura tests integrations examples`) and **Flake8** (`flake8 aura tests integrations examples`) — see [TESTING.md](docs/TESTING.md).
- Typed Python where the surrounding code uses types; match existing naming and structure.

### Scope
Expand All @@ -126,7 +126,7 @@ Follow the [Code of Conduct](CODE_OF_CONDUCT.md). We welcome autonomous logical

```bash
pytest
black aura tests
black aura tests integrations examples
flake8 aura tests
```

Expand Down Expand Up @@ -174,7 +174,7 @@ When in doubt, search the repo for the symbol or term you changed and update doc
1. **Link an issue** — `Fixes #123` or `Refs #123` in the PR description.
2. **Branch** — feature branch on your fork, not direct commits to upstream `main`.
3. **Implement** — follow [Ways to contribute](#ways-to-contribute) and [Ripple effects](#ripple-effects-if-you-change-x-update-y).
4. **Verify locally** — `pytest`, `black aura tests`, `flake8 aura tests`.
4. **Verify locally** — `pytest --ignore=tests/integration`, `black aura tests integrations examples`, `flake8 aura tests integrations examples`.
5. **CHANGELOG** — `[Unreleased]` entry when user-visible.
6. **PR template** — complete [pull request template](.github/PULL_REQUEST_TEMPLATE.md) honestly.
7. **Push** — open PR to `ARPAHLS/aura` `main`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ CLI: `aura agent create`, `aura run`, `aura export`, `aura compare`, `aura expor
| Topic | Links |
| :--- | :--- |
| **Start** | [getting-started.md](docs/getting-started.md) · [concepts.md](docs/concepts.md) · [using-aura.md](docs/using-aura.md) |
| **Integration** | [skillware-integration.md](docs/skillware-integration.md) · [sequencer.md](docs/sequencer.md) |
| **Integration** | [guides/aura-on-skillware.md](docs/guides/aura-on-skillware.md) · [skillware-integration.md](docs/skillware-integration.md) · [sequencer.md](docs/sequencer.md) |
| **Identity & audit** | [trust-paths.md](docs/trust-paths.md) · [outputs.md](docs/outputs.md) |
| **Compare & position** | [comparison.md](docs/comparison.md) · [ROADMAP.md](docs/ROADMAP.md) |
| **Contribute** | [CONTRIBUTING.md](CONTRIBUTING.md) · [Agent workflow](docs/contributing/ai_native_workflow.md) · [TESTING.md](docs/TESTING.md) · [PUBLISHING.md](docs/PUBLISHING.md) · [CHANGELOG.md](CHANGELOG.md) |
Expand Down
6 changes: 6 additions & 0 deletions aura/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def _attach_profile_observers(self) -> None:
for entry in self.profile.observers:
if not isinstance(entry, dict):
continue
preset = entry.get("preset")
if preset == "monitor":
from aura.observers.presets.monitor import create_monitor_observer

self._observers.append(create_monitor_observer(self, entry))
continue
obs_id = entry.get("id")
if not obs_id:
continue
Expand Down
24 changes: 22 additions & 2 deletions aura/hosts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
"""Host adapters — Skillware and mock skills."""
"""Host adapters — ToolHost protocol, Skillware reference, mock skills."""

from aura.hosts.manifest import manifest_snapshot_hash, manifest_to_rules, merge_manifest_into_rules
from aura.hosts.mock import MockSkill, MockSkillRegistry
from aura.hosts.protocol import SkillExecutor, ToolHost
from aura.hosts.skillware import SkillwareHost, skillware_available
from aura.hosts.skillware_adapter import (
SkillwareRegistrySkill,
load_registry_skill,
load_registry_skills,
)

__all__ = ["MockSkill", "MockSkillRegistry", "SkillwareHost", "skillware_available"]
__all__ = [
"MockSkill",
"MockSkillRegistry",
"SkillExecutor",
"SkillwareHost",
"SkillwareRegistrySkill",
"ToolHost",
"load_registry_skill",
"load_registry_skills",
"manifest_snapshot_hash",
"manifest_to_rules",
"merge_manifest_into_rules",
"skillware_available",
]
56 changes: 56 additions & 0 deletions aura/hosts/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Skill manifest → session constraint rules (host bind)."""

from __future__ import annotations

from hashlib import sha256
import json
from typing import Any


def manifest_to_rules(manifest: dict[str, Any]) -> list[dict[str, Any]]:
"""Convert a skill manifest guardrails block into declarative constraint rules."""
if not manifest:
return []
rules: list[dict[str, Any]] = []
guardrails = (
manifest.get("guardrails") if isinstance(manifest.get("guardrails"), dict) else manifest
)

allow = guardrails.get("allow_tools") or guardrails.get("allow")
if allow:
rules.append({"type": "allow_tools", "tools": list(allow)})

deny = guardrails.get("deny_tools") or guardrails.get("deny")
if deny:
rules.append({"type": "deny_tools", "tools": list(deny)})

confirm = guardrails.get("confirm_before")
if confirm:
tools = confirm.get("tools") if isinstance(confirm, dict) else confirm
if tools:
rules.append({"type": "confirm_before", "tools": list(tools)})

limit = guardrails.get("max_tokens_per_step") or guardrails.get("max_tokens")
if limit is not None:
rules.append({"type": "max_tokens_per_step", "limit": int(limit)})

return rules


def manifest_snapshot_hash(skill_id: str, manifest: dict[str, Any]) -> str:
blob = json.dumps({"skill_id": skill_id, "manifest": manifest}, sort_keys=True)
return sha256(blob.encode()).hexdigest()[:16]


def merge_manifest_into_rules(
rules: list[dict[str, Any]],
skill_id: str,
manifest: dict[str, Any],
) -> list[dict[str, Any]]:
"""Append manifest-derived rules; tag each with bind metadata for audit."""
merged = list(rules)
for rule in manifest_to_rules(manifest):
tagged = dict(rule)
tagged["_bind"] = {"skill_id": skill_id, "source": "manifest"}
merged.append(tagged)
return merged
9 changes: 7 additions & 2 deletions aura/hosts/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@


class MockSkill:
"""Minimal skill with a tool → handler map."""
"""Minimal skill with a tool → handler map and optional manifest guardrails."""

def __init__(
self, skill_id: str, handlers: dict[str, Callable[[dict[str, Any]], Any]] | None = None
self,
skill_id: str,
handlers: dict[str, Callable[[dict[str, Any]], Any]] | None = None,
*,
manifest: dict[str, Any] | None = None,
) -> None:
self.skill_id = skill_id
self._handlers = dict(handlers or {})
self.manifest = dict(manifest or {})

def register(self, tool: str, handler: Callable[[dict[str, Any]], Any]) -> None:
self._handlers[tool] = handler
Expand Down
31 changes: 31 additions & 0 deletions aura/hosts/protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""ToolHost protocol — host-agnostic adapter contract."""

from __future__ import annotations

from typing import Any, Protocol


class SkillExecutor(Protocol):
"""Minimal skill surface for host adapters."""

skill_id: str

def execute(self, tool: str, args: dict[str, Any] | None = None) -> Any: ...


class ToolHost(Protocol):
"""
Any capability runtime that registers skills and routes execution through
the membrane egress (policy + audit).
"""

def register(self, skill: SkillExecutor) -> None: ...

def execute(
self,
skill_id: str,
tool: str,
args: dict[str, Any] | None = None,
*,
step_id: str | None = None,
) -> Any: ...
Loading
Loading