Compare commits

..
Author SHA1 Message Date
tevansandClaude Opus 5 cdd7686638 Set min_messages_between to 1
Allows a surface on consecutive messages, leaving cooldown_seconds as
the only spacing gate. At 2 a memory could never follow the one before
it, which suppressed the third hit in a run of related prompts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:57:58 +01:00
tevansandClaude Opus 5 0beaac2133 Loosen surfacing governance defaults
Local tuning — recall was too quiet in practice. After one surface the
old defaults required 10 further messages AND 300s before another, so a
second relevant memory almost never appeared in a working session.

  threshold                0.30 -> 0.25
  max_surfaces_per_session    5 -> 12
  min_messages_between       10 -> 2
  cooldown_seconds          300 -> 30

test_first_surface_ignores_spacing now passes an explicit config, so it
exercises the spacing mechanism rather than tracking whatever the
defaults happen to be. The value pin is updated to match.

Not for upstream — keep off fix/passport-traits-email.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:09:16 +01:00
tevansandClaude Opus 5 e9c13ed7a1 Sort imports in test_citizen_classes.py
Two pre-existing ruff I001 violations in the file this PR touches:
pathlib grouped after pytest at module level, and a stdlib import
adjacent to a first-party one inside test_update_cli_accepts_class_with_all.

Formatting only — no test behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:53:01 +01:00
tevansandClaude Opus 5 e0e9b289cc Add regression tests for passport traits and email
Covers both halves of the fix: that the templates reference {{TRAITS}}
and {{EMAIL}}, and that a created agent ends up with the rendered values.

Includes the no-flag case — omitting --traits must leave an empty string,
since the identity hook skips the line when falsy and a missing key would
change existing behaviour.

All five fail against the pre-fix templates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:50:20 +01:00
tevansandClaude Opus 5 2efb4e8645 Populate traits and email in agent passport templates
`drone @spawn create --traits "..."` accepted the flag and silently
discarded it: the TRAITS placeholder is built in placeholders.py but no
template referenced it. Same for EMAIL — the registry recorded @<agent>
while the passport had no email key, so the identity hook rendered
"Email: unknown" for every agent.

Both placeholders were already wired; only the templates needed to use
them. Applied to aipass_framework and project_agent alike.

The identity hook reads traits as either list or string and skips the
line when falsy, so an agent created without --traits is unchanged.

Also regenerates aipass_framework's .template_registry.json, which drops
seven stale .pytest_cache entries for files not present in the template.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:40:13 +01:00
AIPass 5e36909e53 Merge pull request #709 from AIOSAI/dependabot/github_actions/actions/setup-python-7.0.0
ci(deps): bump actions/setup-python from 6.3.0 to 7.0.0
2026-07-26 06:53:33 -07:00
dependabot[bot] 948c65725c ci(deps): bump actions/setup-python from 6.3.0 to 7.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.3.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/ece7cb06caefa5fff74198d8649806c4678c61a1...5fda3b95a4ea91299a34e894583c3862153e4b97)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 08:03:10 +00:00
13 changed files with 74 additions and 66 deletions
+4 -4
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
# Hash-pinned tool install (Scorecard: Pinned-Dependencies). Pins ruff to
@@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- run: |
@@ -55,7 +55,7 @@ jobs:
# makes every file look born at HEAD, so every README false-fails as
# "stale". Full history makes CI match a local audit exactly.
fetch-depth: 0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- run: |
@@ -77,7 +77,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- run: |
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
attestations: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
# Hash-pinned tool install (Scorecard: Pinned-Dependencies).
+1 -1
View File
@@ -41,6 +41,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
sarif_file: results.sarif
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
# Upgrade pip first: pip-audit scans the whole environment, and the
@@ -44,7 +44,7 @@ jobs:
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
- uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: python
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
- uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
@@ -26,10 +26,10 @@ from aipass.memory.apps.handlers.json import json_handler
DEFAULT_CONFIG: Dict[str, Any] = {
"enabled": True,
"threshold": 0.3,
"max_surfaces_per_session": 5,
"min_messages_between": 10,
"cooldown_seconds": 300,
"threshold": 0.25,
"max_surfaces_per_session": 12,
"min_messages_between": 1,
"cooldown_seconds": 30,
}
+10 -7
View File
@@ -142,20 +142,23 @@ class TestShouldSurfaceHappy:
def test_first_surface_ignores_spacing(self):
"""Fresh session: first prompt with high relevance surfaces immediately."""
# Explicit config: this exercises the spacing mechanism, so it must not
# move whenever DEFAULT_CONFIG is retuned.
cfg = {"min_messages_between": 10, "cooldown_seconds": 300}
state = new_state()
state["messages_since_last"] = 1
ok, reason, updated = should_surface("compass-1", 0.8, state, current_time=100.0)
ok, reason, updated = should_surface("compass-1", 0.8, state, cfg, current_time=100.0)
assert ok is True
assert "ready" in reason.lower()
for i in range(9):
updated = record_message(updated)
ok, reason, _ = should_surface("compass-2", 0.8, updated, current_time=100.0 + 400 + i)
ok, reason, _ = should_surface("compass-2", 0.8, updated, cfg, current_time=100.0 + 400 + i)
assert ok is False
assert "spacing" in reason.lower()
updated = record_message(updated)
ok, _, _ = should_surface("compass-2", 0.8, updated, current_time=600.0)
ok, _, _ = should_surface("compass-2", 0.8, updated, cfg, current_time=600.0)
assert ok is True
@@ -246,7 +249,7 @@ class TestConfigMerging:
assert "threshold" in reason.lower()
def test_default_config_values_match(self):
assert DEFAULT_CONFIG["threshold"] == 0.3
assert DEFAULT_CONFIG["max_surfaces_per_session"] == 5
assert DEFAULT_CONFIG["min_messages_between"] == 10
assert DEFAULT_CONFIG["cooldown_seconds"] == 300
assert DEFAULT_CONFIG["threshold"] == 0.25
assert DEFAULT_CONFIG["max_surfaces_per_session"] == 12
assert DEFAULT_CONFIG["min_messages_between"] == 1
assert DEFAULT_CONFIG["cooldown_seconds"] == 30
@@ -5,11 +5,6 @@
"name": ".ai_mail.local",
"path": ".ai_mail.local"
},
"d002": {
"has_branch_placeholder": false,
"name": ".pytest_cache",
"path": ".pytest_cache"
},
"d003": {
"has_branch_placeholder": false,
"name": ".aipass",
@@ -119,16 +114,6 @@
"has_branch_placeholder": false,
"name": "integrations",
"path": "apps/integrations"
},
"d025": {
"has_branch_placeholder": false,
"name": "v",
"path": ".pytest_cache/v"
},
"d026": {
"has_branch_placeholder": false,
"name": "cache",
"path": ".pytest_cache/v/cache"
}
},
"files": {
@@ -211,7 +196,7 @@
"path": ".trinity/observations.json"
},
"f014": {
"content_hash": "d523dee2ef39",
"content_hash": "98f1e33dace0",
"has_branch_placeholder": false,
"name": "passport.json",
"path": ".trinity/passport.json"
@@ -282,12 +267,6 @@
"name": "README.md",
"path": "apps/plugins/README.md"
},
"f026": {
"content_hash": "3ed731b65d06",
"has_branch_placeholder": false,
"name": ".gitignore",
"path": ".pytest_cache/.gitignore"
},
"f027": {
"content_hash": "024209a8c889",
"has_branch_placeholder": true,
@@ -402,24 +381,6 @@
"name": "requirements.project.txt",
"path": "requirements.project.txt"
},
"f046": {
"content_hash": "37dc88ef9a0a",
"has_branch_placeholder": false,
"name": "CACHEDIR.TAG",
"path": ".pytest_cache/CACHEDIR.TAG"
},
"f047": {
"content_hash": "73fd6fccdd80",
"has_branch_placeholder": false,
"name": "README.md",
"path": ".pytest_cache/README.md"
},
"f048": {
"content_hash": "0575fdabafa9",
"has_branch_placeholder": false,
"name": "nodeids",
"path": ".pytest_cache/v/cache/nodeids"
},
"f049": {
"content_hash": "5610e3ccaf8b",
"has_branch_placeholder": false,
@@ -429,7 +390,7 @@
},
"metadata": {
"description": "Template file tracking registry for ID-based updates",
"last_updated": "2026-07-17",
"last_updated": "2026-07-27",
"version": "1.0.0"
}
}
@@ -14,12 +14,14 @@
"alias": "",
"path": "{{CWD}}",
"module": "{{MODULE}}",
"email": "{{EMAIL}}",
"created": "{{DATE}}",
"git_branch": "work/{{branchname}}"
},
"identity": {
"citizen_class": "{{CITIZEN_CLASS}}",
"role": "{{ROLE}}",
"traits": "{{TRAITS}}",
"purpose": "{{PURPOSE_BRIEF}}",
"what_i_do": [],
"what_i_dont_do": []
@@ -14,12 +14,14 @@
"alias": "",
"path": "{{CWD}}",
"module": "{{MODULE}}",
"email": "{{EMAIL}}",
"created": "{{DATE}}",
"git_branch": "main"
},
"identity": {
"citizen_class": "manager",
"role": "{{ROLE}}",
"traits": "{{TRAITS}}",
"purpose": "{{PURPOSE_BRIEF}}",
"what_i_do": [],
"what_i_dont_do": []
+42 -2
View File
@@ -13,10 +13,9 @@ class-aware update, and backward compatibility.
"""
import json
import pytest
from pathlib import Path
import pytest
# =============================================================================
# CLASS REGISTRY TESTS
@@ -172,6 +171,7 @@ class TestClassAwareUpdate:
def test_update_cli_accepts_class_with_all(self):
"""update aipass_framework --all should parse correctly and call update_all with class filter."""
from unittest.mock import patch
from aipass.spawn.apps.modules.update import handle_update
# Mock update_all to isolate from real branch state
@@ -220,6 +220,16 @@ class TestTemplateStructure:
passport = json.loads((tpl / ".trinity" / "passport.json").read_text())
assert passport["identity"]["citizen_class"] == "{{CITIZEN_CLASS}}"
@pytest.mark.parametrize("class_name", ["aipass_framework", "project_agent"])
def test_template_passport_has_traits_and_email_placeholders(self, class_name):
"""Agent template passports reference TRAITS and EMAIL — unreferenced, both are built and discarded."""
from aipass.spawn.apps.handlers.class_registry import get_template_dir
tpl = get_template_dir(class_name)
passport = json.loads((tpl / ".trinity" / "passport.json").read_text())
assert passport["identity"]["traits"] == "{{TRAITS}}"
assert passport["branch_info"]["email"] == "{{EMAIL}}"
def test_no_agent_template_dir(self):
"""Old agent.template directory should not exist."""
spawn_root = Path(__file__).parents[1]
@@ -286,6 +296,36 @@ class TestAgentScaffoldContent:
passport = json.loads((target / ".trinity" / "passport.json").read_text())
assert passport["identity"]["role"] == "Data Analyst"
def test_created_agent_passport_has_traits(self, tmp_path):
"""Passport should include the agent's traits if provided."""
from aipass.spawn.apps.modules.core import _spawn_agent
target = tmp_path / "traits_test"
_spawn_agent(str(target), role="Analyst", traits="curious, terse", purpose="Reports")
passport = json.loads((target / ".trinity" / "passport.json").read_text())
assert passport["identity"]["traits"] == "curious, terse"
def test_created_agent_passport_traits_empty_without_flag(self, tmp_path):
"""Omitting traits leaves an empty string — the identity hook skips the line when falsy."""
from aipass.spawn.apps.modules.core import _spawn_agent
target = tmp_path / "no_traits_test"
_spawn_agent(str(target), purpose="Testing default")
passport = json.loads((target / ".trinity" / "passport.json").read_text())
assert passport["identity"]["traits"] == ""
def test_created_agent_passport_has_email(self, tmp_path):
"""Passport carries the branch address, so identity does not render 'Email: unknown'."""
from aipass.spawn.apps.modules.core import _spawn_agent
target = tmp_path / "email_test"
_spawn_agent(str(target), purpose="Testing email")
passport = json.loads((target / ".trinity" / "passport.json").read_text())
assert passport["branch_info"]["email"] == "@email_test"
# =============================================================================
# MULTI-AGENT COEXISTENCE TESTS