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
18 changes: 0 additions & 18 deletions .github/workflows/aippocampus-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,21 +269,3 @@ jobs:
run: |
# Covers recurring #140/#242/#402 /var and /private/var path-identity regressions without repeating the full PR tier.
python -m unittest tests.aippocampus.test_path_identity tests.aippocampus.test_run_tests_tiers tests.aippocampus.test_macos_install_smoke_workflow -v

full-tests:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Full test tier
run: python tools/aippocampus/run_tests.py --tier full
29 changes: 29 additions & 0 deletions .github/workflows/full-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: AIppocampus Full Test Tier

on:
workflow_dispatch:

permissions:
contents: read

jobs:
full-tests:
name: Full test tier
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install full test dependencies
run: python -m pip install -e ".[dev,benchmark,openai-agents-smoke]"

- name: Full test tier
run: python tools/aippocampus/run_tests.py --tier full
11 changes: 6 additions & 5 deletions docs/architecture/architecture-debt-register.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ def chooser_tail_supported(tail: Sequence[str]) -> bool:

def _action_candidates(payload: Mapping[str, Any]) -> list[dict[str, Any]]:
candidates: list[dict[str, Any]] = []
for raw in [
payload.get("foreground_action"),
*(payload.get("safe_next_actions") or []),
*(payload.get("choices") if isinstance(payload.get("choices"), list) else []),
*(payload.get("write_actions") if isinstance(payload.get("write_actions"), list) else []),
]:
raw_candidates: list[Any] = [payload.get("foreground_action")]
for key in ("safe_next_actions", "choices", "write_actions"):
value = payload.get(key)
if isinstance(value, list):
raw_candidates.extend(value)
for raw in raw_candidates:
if not isinstance(raw, Mapping):
continue
action = normalize_foreground_action(raw)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def module_exception_payload(
else []
)
action = search_actions[0] if search_actions else script_recovery_action(script_name, args)
payload = (
payload: dict[str, Any] = (
{
"ok": False,
"error": cli_error_object(
Expand Down Expand Up @@ -231,7 +231,8 @@ def module_exception_payload(


def render_module_exception_text(payload: dict[str, Any]) -> str:
error = payload.get("error") if isinstance(payload.get("error"), dict) else {}
raw_error = payload.get("error")
error: Mapping[str, Any] = raw_error if isinstance(raw_error, Mapping) else {}
action = payload.get("foreground_action")
action_map = action if isinstance(action, Mapping) else {}
lines = [
Expand All @@ -252,7 +253,8 @@ def handle_module_exception(
stderr_text: str = "",
) -> int:
payload = module_exception_payload(script_name, args, exc, stderr_text=stderr_text)
error = payload.get("error") if isinstance(payload.get("error"), dict) else {}
raw_error = payload.get("error")
error: Mapping[str, Any] = raw_error if isinstance(raw_error, Mapping) else {}
exit_code = cli_exit_code_for_error_code(str(error.get("code") or "runtime_error"))
if args_request_json(args):
emit_public_text(json.dumps(payload, ensure_ascii=False, indent=2), end="")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
"output_boundary",
"policy_boundary",
"privacy_boundary",
"risk_boundary",
"source_boundary",
"source_reopen_boundary",
"suppression_boundary",
"write_boundary",
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ def build_perturbation_packet(
reason_codes.append("inversion_requires_source_reopen_or_conflict_review")
if policy.get("stale_conflict_checks_required"):
reason_codes.append("stale_conflict_checks_required")
for reason in layer_diagnostics["reason_codes"]:
raw_layer_reasons = layer_diagnostics.get("reason_codes")
layer_reasons = raw_layer_reasons if isinstance(raw_layer_reasons, list) else []
for reason in layer_reasons:
if reason not in reason_codes:
reason_codes.append(str(reason))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ def build_total_hexagram_encoding(
if explicit_reviewed_state is None:
observed: dict[int, list[dict[str, Any]]] = {line: [] for line in range(1, 7)}
for signal in line_signals:
line = _line_index(signal.get("line"))
line_index = _line_index(signal.get("line"))
value = _line_value(signal.get("value"))
if line is None or value is None:
if line_index is None or value is None:
continue
if _private_scope(signal):
blocked = True
observed[line].append(
observed[line_index].append(
{
"value": value,
"authority": "blocked",
Expand All @@ -210,7 +210,7 @@ def build_total_hexagram_encoding(
)
continue
refs = _safe_refs(signal.get("source_refs"))
observed[line].append(
observed[line_index].append(
{
"value": value,
"authority": "source_backed" if refs else "unknown",
Expand All @@ -221,8 +221,8 @@ def build_total_hexagram_encoding(
for line, rows in observed.items():
if not rows:
continue
values = {row["value"] for row in rows}
if len(values) > 1:
row_values = {row["value"] for row in rows}
if len(row_values) > 1:
ambiguous = True
line_slots[line] = {
"line": line,
Expand Down Expand Up @@ -292,15 +292,24 @@ def build_total_hexagram_encoding(
"symbolic_advice_included": False,
},
}
if complete:
lines_tuple = tuple(int(value) for value in known_values) # type: ignore[arg-type]
complete_values = [value for value in known_values if value in (0, 1)]
if complete and len(complete_values) == 6:
line_values = tuple(int(value) for value in complete_values)
lines_tuple = (
line_values[0],
line_values[1],
line_values[2],
line_values[3],
line_values[4],
line_values[5],
)
hexagram = hexagram_from_lines(lines_tuple)
hexagram_projection = hexagram.to_public_dict()
hexagram_projection["bits_bottom_to_top"] = hexagram_projection["bitstring_bottom_to_top"]
result["hexagram"] = hexagram_projection
result["macro_state_hint"] = _state_hint(
project=project,
lines=lines_tuple, # type: ignore[arg-type]
lines=lines_tuple,
changing_lines=changing_lines,
active_layer=active_layer,
momentum=momentum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,58 @@ def _select_initial_foreground_action(
foreground_action,
context.recall_selector,
)
if not current_source_route_policy.primary_deepen_followthrough_reopenable(
context.memory_packets,
):
registry_fallback = recall_choices.registry_source_search_fallback_action(
context.recovery_cue
)
if registry_fallback:
registry_fallback["route_choice_posture"] = "route_note_requires_source_search"
registry_fallback["why"] = (
"The top recall route is route-note navigation without a "
"reopenable clean-source handle; search registered sources for "
"the original cue anchors before deepening or claiming."
)
foreground_action = registry_fallback
safe_next_actions = [
{
"id": "refine_low_specificity_recall_cue",
"label": "Refine low-specificity recall cue",
"tool_name": "agent_recall",
"arguments_template": {"query": "{tighter_cue}", "max": 3},
"requires": ["tighter_cue"],
"template_only": True,
"command_template": 'aippocampus agent recall "{tighter_cue}" --json',
"route_choice_posture": "route_note_requires_source_search",
"mutation_risk": "read_only",
"claim_boundary": "no_claim_before_reopen",
"why": (
"If source search does not find the right anchor, "
"tighten the cue with a more specific phrase, object, "
"person, or time clue."
),
}
]
else:
weak_route_recovery_card = _weak_route_recovery_card()
foreground_action = {
"action_id": "recover_route_note_without_source_ref",
"label": "Recover route-note recall",
"tool_name": "search_memory",
"why": (
"The top route-note candidate has no reopenable source "
"handle; provide a more specific cue before relying on it."
),
"mutation_risk": "read_only",
"claim_boundary": "no_claim_before_reopen",
} | context.search_fields
return ForegroundSelection(
foreground_action=foreground_action,
miss_recovery_card=miss_recovery_card,
weak_route_recovery_card=weak_route_recovery_card,
safe_next_actions=safe_next_actions,
)
foreground_action = recall_choices.with_low_specificity_foreground_action(
foreground_action,
metrics=context.metrics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any

from aippocampus_runtime.recall import associative_path_foreground_gate as apw_gate
from aippocampus_runtime.source.io_kernel import source_ref_identity_key
from aippocampus_runtime.source.query_match_gate import query_match_gate


Expand All @@ -23,6 +24,61 @@ def primary_current_source_reopenable(memory_packets: list[dict[str, Any]]) -> b
)


def primary_deepen_followthrough_reopenable(memory_packets: list[dict[str, Any]]) -> bool:
"""Return whether the primary route is safe to expose as agent_deepen.

Low-specificity recall sometimes surfaces route-note navigation scents. A
route note is useful orientation, but it should not become the first
foreground `agent_deepen` action unless it carries joined clean-source
evidence that `agent_deepen` can reopen.
"""

if not memory_packets:
return False
primary = memory_packets[0]
if primary.get("output_mode") != "reopenable_route":
return False
if not _route_note_like(primary):
return True
return _route_note_has_joined_source_ref(primary)


def _route_note_like(packet: Mapping[str, Any]) -> bool:
markers = {
str(packet.get("route_kind") or ""),
str(packet.get("matched_cue_family") or ""),
str(packet.get("origin") or ""),
str(packet.get("route_origin") or ""),
str(packet.get("source") or ""),
}
return any("route_note" in marker.casefold() for marker in markers)


def _route_note_has_joined_source_ref(packet: Mapping[str, Any]) -> bool:
for key in ("joined_evidence_refs", "source_refs"):
value = packet.get(key)
if isinstance(value, list) and any(_route_note_ref_reopenable(item) for item in value):
return True
source_ref = packet.get("source_ref")
return _source_ref_reopenable(source_ref)


def _route_note_ref_reopenable(value: Any) -> bool:
if not isinstance(value, Mapping):
return False
nested = value.get("source_ref")
if _source_ref_reopenable(nested):
return True
return _source_ref_reopenable(value)


def _source_ref_reopenable(value: Any) -> bool:
if not isinstance(value, Mapping):
return False
source_id, thread_key, message_id, turn_id, line = source_ref_identity_key(value)
return bool((source_id or thread_key) and (message_id or turn_id or line))


def apw_card_allows_primary(
*,
should_replace: bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
canonical_foreground_action_fields,
foreground_shell_action,
)
from aippocampus_runtime.foreground_compact_language import compact_frontstage_projection
from aippocampus_runtime.model.routing import DEFAULT_DEEPSEEK_API_KEY_ENV
from aippocampus_runtime.ops.doctors.common import as_dict

Expand Down Expand Up @@ -146,11 +147,18 @@ def compact_provider_doctor_card(report: dict[str, Any]) -> dict[str, Any]:
"frontstage_rule": "readiness and next check first; diagnostics stay in full detail",
},
}
return {
key: value
for key, value in card.items()
if value not in (None, "", [], {})
}
return compact_frontstage_projection(
{
key: value
for key, value in card.items()
if value not in (None, "", [], {})
},
extra_denied_keys={
"audit_json_available",
"boundary_summary",
"full_audit_command",
},
)


def render_text(report: dict[str, Any]) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from aippocampus_runtime import core
from aippocampus_runtime.contracts import canonical_foreground_action_fields
from aippocampus_runtime.foreground_compact_language import compact_frontstage_projection
from aippocampus_runtime.ops.storage_governance_actions import (
candidate_can_offer_compact_apply,
storage_gc_summary_actions,
Expand Down Expand Up @@ -295,6 +296,8 @@ def bounded_cli_projection(
action_fields = canonical_foreground_action_fields(
summary_actions[0],
safe_next_actions=summary_actions,
max_safe_next_actions=1,
safe_next_read_only_only=True,
)
next_steps = [
"Continue normal work when cleanup was not the user goal.",
Expand Down Expand Up @@ -392,6 +395,19 @@ def bounded_cli_projection(
canonical_foreground_action_fields(
primary,
safe_next_actions=[primary, *existing_actions],
max_safe_next_actions=1,
safe_next_read_only_only=True,
)
)
return projection
projection["details_available"] = True
return compact_frontstage_projection(
projection,
extra_denied_keys={
"comparable_metrics_command",
"full_audit_available",
"full_audit_flag",
"operator_audit_command",
"policy_model",
"report_sources",
},
)
Loading
Loading