test_broker.py (AF_UNIX sockets + openat2) and test_sandbox.py (bwrap) are
Linux-only; module-level pytestmark skips them on windows-latest while leaving
Linux runs unchanged. Unblocks the windows-setup CI check (red since the
sandbox build 0b4ba63).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoist per-checker SKIP_DIRS to shared SOURCE_SKIP_DIRS (artifacts/dropbox are
output dirs, not source; no git coupling). Diagnostics: python3->sys.executable,
parse/run failures now fail loud instead of silent 0-errors-clean, and pin
pyright resolution with --pythonpath sys.executable. drone bypasses test-only
broker start_background. Proven all-13-branches-100% deterministic in an
unactivated shell (local==CI).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
src/aipass/common was the only non-citizen directory in the agent namespace.
Per @seedgo design review: moved into @aipass (owner) as aipass.aipass.shared,
content byte-identical. @spawn imports across (blessed shared-infra category,
same as aipass.prax/cli). New subprocess guard test pins the bootstrap-safety
invariant (shared/ loads zero branch deps — aipass init stays pre-drone-safe).
9 import/doc sites updated, 9 documented seedgo bypasses (pre-infra leaf,
stdlib-only by design). aipass 480 + spawn 315 tests green, both audits 100%,
repo-wide zero refs to the old path.
Every autonomous agent can launch inside a kernel-enforced mount namespace
(srt -> bwrap+seccomp): reads stay open (shared live FS preserved, bind-mount not
isolation; own-tree writes land live), but rm/python/find/Write on .git or sibling
trees hit EROFS. /tmp + own tree writable; .git RW devpulse, RO builders. Inert by
default behind AIPASS_SANDBOX_ENABLED (off); flag-off path byte-identical to old.
hooks: srt wrapper + per-role build_policy + broker_secret mask; rm_gate demoted.
drone: out-of-sandbox broker (identity allowlist, openat2 RESOLVE_BENEATH, HMAC
handshake over inherited fd, audit); drone rm via broker when sandboxed.
ai_mail: dispatch gate + broker-fd wiring (fail-loud exit -4, never silent).
aipass: doctor Sandbox group + setup.sh prereqs (LOUD on missing).
Proven by a live 16-check red-team suite. seedgo 100% + 2859 tests green across
all 5 touched branches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same action-gated pattern as the notification handlers — speak() -> 'sound'
return-key. Missed in b26bd7c. Hooks suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Notification handlers (announce, email, stop_sound, tool_sound) return a
'sound' key the engine plays on action instead of calling speak() on every
invocation — quieter and honest (skipped loaders stay silent). Slim
cadence_investigation.md. Tests updated to assert the return-key form. 472/472
hooks green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three ruff call sites called bare `ruff`, absent from the hook subprocess
PATH (ruff lives only in .venv) — logged 'ruff not found' 177x over ~a month,
silently skipping lint+format on every edit. Also `--output-format=text` was
removed from modern ruff. Fixed all three sites to `sys.executable -m ruff`
(the pattern the working pyright leg already uses) + concise format + honest
rc>=2 error logging. Tests now pin the invocation (argv == sys.executable -m
ruff) so a regression fails loud — the subprocess-mock is how this hid.
438/438 hooks tests green; live-verified through the real hook pipeline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewrite the always-injected global prompt from a ~13.8KB encyclopedia
into a ~7.8KB navigation map. The prompt now carries AWARENESS; detail
is fetched on demand via 'drone @agent --help'. Dissolves the harness
~10k-char truncation bug — the old prompt's tail (Hard Rules onward)
silently never arrived; the slim one injects whole.
- drone pinned at top as the router; one drilled reflex: --help before use
- framework tree restored; all 13 agents as 2-3 sentence bios
- introspection named as our term; breadcrumb-first navigation flow
- git its own section (raw git/gh blocked, drone-only, devpulse-writes)
- plans section (DPLAN/FPLAN/PPLAN/RPLAN + 'drone @flow templates')
- @memory chroma awareness (local + global stores, search before cold)
- sub-agent usage section incl. model practice (never fable)
- PROMPT_STYLE-conformant; 7855 chars (cap 8000, measured in chars)
Backup retained: .aipass/aipass_global_prompt.BACKUP-2026-06-09-S211.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause: _HOOK_PATTERN required an action= key that cadence never
emits — it logs the action as the bare second word (fired/skipped).
Extraction failed, so events never reached the styled print_hook_event
renderer (bold-green lightning / dim dot). Pipeline was already correct.
- _HOOK_PATTERN -> bare-word capture: r'\[HOOKS\]\s+(\w+)\s+(\w+)'
- enriched hook event detail (period, offset, short session id)
- corrected docstring that documented the phantom action= format
- tests updated to real production format + real-pipeline test added
- type:ignore on watchdog imports (repo convention)
Verified: 914/914 prax suite green (90 log_watcher). @prax dispatched
for full-pipeline trace; stale monitor process explained the no-show.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cadence redo — verified against the live execution model, not unit tests:
- Counter now advances exactly once per real turn (mtime debounce +
transcript-size token + flock). Fixes the separate-process leapfrog where
global/branch loaders double-incremented and fired erratically.
- Structured [HOOKS] cadence fired|skipped logging; prax monitor renders
hook events distinctly for live visibility.
- Action-gated sound: handlers return a 'sound' key the engine plays only on
real action — skipped loaders are silent (no more false piper every turn).
- Fixed auto_fix.py: leftover speak() NameError (swallowed by broad except)
meant diagnostics silently never ran on any edit. Removed; sound moved to
the error path.
- Tests rewritten to model separate-process execution (leapfrog regression
test added); sound assertions across all refactored handlers. 438 pass.
prax: hook fire/skip event rendering in the live monitor. 913 pass.
README: hardcoded metrics (version/tests/PRs/standards) -> live PyPI+codecov
badges and qualitative wording; killed the 33-vs-36 drift. CHANGELOG W24.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stop re-injecting the global + branch prompts every turn (~3k tokens/turn).
They now fire together every 5th turn; the prior injection persists in context
between fires. Identity + email stay every-turn.
- apps/modules/cadence.py: per-session turn counter (/tmp/aipass-cadence-
{session_id}.json), should_fire(loader)/reset_counter(), DEFAULTS + deep-merge
config (api provider.py pattern). 'drone @hooks cadence' introspection.
- global_loader.py + branch_loader.py: cadence guard via importlib (crash-
isolated); non-fire turn returns empty.
- compact.py: PreCompact resets counter to -1 -> next turn = 0 = all fire
(rebuild context after compaction). New session = fresh counter = all fire.
- hooks_json/custom_config/cadence_config.json: tunable knob (period/offsets/
enabled), one file, no code edits. Data lives in the json home, not the code
dir. Missing file = code DEFAULTS = safe.
- .seedgo/bypass: documented stdlib-json config read (json_handler N/A for a
dispatch engine).
435 tests pass (26 new), seedgo 100%, pyright 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Memory files now reconcile to template, not just clean known fields:
- normalize.py: rewrote from field-targeted cleanup to template-conformance —
strips ANY key not in the template at every level (root/metadata/limits/status).
Kills legacy orphans (old 'st' blocks, active_tasks, current_lines, max_lines)
that field-targeted cleanup was blind to. Fixed _MEMORY_ROOT path (parents[3])
that silently skipped template loading in production.
- memory_watcher.py: wired normalize_memory_file into both scan paths
(check_and_rollover + on_modified) with a write-loop guard — drift now
self-heals on every trigger, no manual run needed.
- line_counter.py: stop writing current_lines (entry-count is the only metric).
- LOCAL/OBSERVATIONS templates: removed line-count fields.
Entry-count is the sole rollover metric, both files, all branches.
873 tests pass, seedgo 100%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reply and send silently truncated multi-line bodies to the first CLI arg.
handle_reply(args[1]) and parse_send_args(rest[1]) dropped args[2:]/rest[2:]
when a body word-split into multiple args. Now join all remaining args.
Backwards-compatible; single-arg messages unchanged. +6 tests (718 total).
Found via @hooks replies arriving as first-line-only (60/48 chars).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
publish.yml github-release job now signs the wheel + sdist via
sigstore/gh-action-sigstore-python (pinned v3.3.0 / 04cffa1d), keyless OIDC,
and attaches the .sigstore.json bundles to the GitHub Release through the
existing dist/* glob. Added id-token: write to the job for OIDC.
PyPI uploads were already attested (Trusted Publishing); Scorecard's
Signed-Releases check inspects GitHub Releases, which only carried bare wheels
-> score 0. .sigstore.json is in Scorecard's recognized signatureExtensions.
Verified: action globs ./dist/*.whl ./dist/*.tar.gz (action.py:202), auto-attach
gated on release-event (we trigger on push:tags) so we upload via dist/* and set
release-signing-artifacts:false. First live proof = next v* tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
e2e-wheel.yml was the only workflow missing a top-level permissions: block
(added during cross-OS work after PR #624 hardened the rest), so it ran with
default broad GITHUB_TOKEN scopes -> OpenSSF Scorecard Token-Permissions = 0.
Add 'permissions: contents: read' to match the other 7 workflows. CHANGELOG
W24 entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seedgo hook-cruft purge + raise CI standards floor to 100%
Two changes:
1. refactor(seedgo): archive pre-DPLAN-0184 hook cruft (FPLAN-0241) — orphaned bridge/probe/manifest modules + tests moved to .archive/; README/bypass/prompts updated. 1045 tests green.
2. ci(seedgo-audit): raise the standards floor 80%->100%.
NOTE — the seedgo-audit check will go RED by design. With the 100% floor, the 6 branches at 99% (aipass/api/drone/flow/prax/seedgo) are now caught. This PR is to OBSERVE the gate enforcing in CI; it is not merge-bound until those 6 branches reach a genuine 100%.
Verification audit caught a gap: spawn create copies templates/builder/ tree
directly (DEFAULT_TEMPLATE), but builder/.trinity/local.json lacked the todos[]
schema — so freshly spawned branches would not inherit it. Seeded todos[] +
max_todos:10 + todo_text_max_chars:200 + operational note to match @memory's
LOCAL.template.json. Now both spawn-create and template-push paths produce
todos[].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spawn's pre-merge JSON backups dropped a .recovery/ dir at each branch root,
which had accumulated 242 stale auto-gen DASHBOARD backups across 10 branches
(the original .recovery report that started this whole investigation).
- aipass.common.json_ops.backup_json gained optional backup_dir param
(default unchanged = file_path.parent/.recovery, backward-compatible).
- spawn update engine (update_ops.py _merge_json) now passes
branch_dir/.spawn/.recovery as the backup dest -> backups land under the
spawn-managed .spawn/ dir, one namespace, not cluttering branch roots.
- Memory stays in the safety net: no memory-exclusion added; the engine just
never touches .trinity/DASHBOARD on update so it never backs them up.
- 2 new tests (unit: custom backup_dir; integration: backup lands in
.spawn/.recovery). 315 spawn + 438 aipass tests green; seedgo 100% both.
Stale .recovery backups swept separately (untracked/gitignored, local hygiene).
.recovery/ gitignore pattern already covers .spawn/.recovery/.
TDPLAN-0006 P4 — final phase. Closes the spawn update-safety + consolidation
work (P0 dry-run-default, P1 #636 engine, P2 shared lib, P3 import kill, P4
backup relocate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
init_flow.py:896 was the one place aipass imported spawn's Python directly:
from aipass.spawn.apps.modules.sync_registry import sync_registry
Replaced with subprocess.run(['drone','@spawn','sync-registry','--fix']) — the
command spawn already exposes — matching the aipass init agent -> drone @spawn
create pattern. Graceful degradation preserved: FileNotFoundError (no drone),
non-zero exit, and timeout are all silently skipped so a registry-sync hiccup
never hard-fails an init update. Safe because init update runs on an existing
project where drone is installed (NOT the pre-drone fresh-init path).
aipass branch now has ZERO direct imports of another branch's ENGINE code.
Remaining cross-branch imports are shared SERVICE layers only (cli Rich UI,
prax logger used in 347 files, trigger events) — infrastructure, not duplication.
Verified: zero aipass.spawn imports in .py code; fresh aipass init still
scaffolds (bootstrap pre-drone intact, 69 tests); 438 tests green (4 new for
the subprocess path: success/failure/missing-drone/timeout); seedgo 100%.
TDPLAN-0006 P3. P4 (.recovery relocate) is the last phase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#636: drone @spawn update would scramble every branch's identity/memory in
one command. On a branch created seconds earlier, --dry-run proposed 30 renames
rotating identity dirs (apps->.trinity->.seedgo->.claude->.archive->.aipass),
README->DASHBOARD, and deep-merged stale template into live .trinity/. Root
cause: the CREATE path regenerated template-registry IDs in filesystem-walk
order (!= the master's hand-crafted IDs), so content-hash + rename-detection
saw a mismatch on a pristine branch. update --all would have destroyed all 13
citizens at once.
P0 — safety by default:
- update + repair are now dry-run by default; --apply required to write.
Forgotten flag = safe preview-only no-op. --dry-run kept as alias.
- doctor_fix.py repair suggestions emit the matching --apply form
(+ aipass test_doctor_fix updated to the new contract).
P1 — engine rebuild (update_ops.py v2.0):
- Path-based named-managed-files model replaces whole-tree hash-diff +
rename-detection. ID divergence is moot — IDs are no longer used.
- .trinity/*, DASHBOARD.local.json, artifacts/birth_certificate.json,
.seedgo/bypass.json = delivered on CREATE only, NEVER touched on update.
- Old ID engine (change_detection.py, reconcile.py) + orphaned tests deleted.
Verified on fresh sandbox: update --dry-run = 0 renames / 0 updates / 0
additions (create==update invariant); no-flag run = dry-run preview, filesystem
byte-identical; 313 spawn tests green; seedgo 100% (all 36 standards).
Closes#636. P2/P3/P4 (shared lib, seam, .recovery relocate) to follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops git check-ignore + git log from readme_check. Runtime dirs tolerated via
static list (_is_runtime_artifact); freshness checks date-presence only, no
history comparison. Local-CI parity proven 13/13 both ways (working tree +
git archive clean checkout). Invariant: a checker never consults git or
.gitignore; only bypass.json excludes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Windows Test + macOS Test only triggered on changes to setup.sh / drone/cli.py
/ handlers/__init__.py / pyproject.toml, but branch protection requires their
checks (windows-setup / macos-setup). On any PR not touching those paths the
workflows never ran, so GitHub parked the required checks as 'Expected —
waiting for status' forever, blocking merge — exactly what happened to PR #631
(the tests last ran + passed yesterday on the version-bump commit; tonight's
commits didn't match the filter so they never fired). The OS code is fine:
e2e-wheel's windows-latest + macos-latest passed on the same commits.
Fix: drop the paths filter; run on every push/PR to main/dev like the other
required lanes (CI/lint/coverage/security/e2e are none of them path-filtered).
A required status check must never be path-filtered or it stalls PRs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dependency-scan (pip-audit) was red: it scans the whole env, and the runner's
bundled pip 26.1.1 carries PYSEC-2026-196 (fixed in 26.1.2). The job was the
only CI job not upgrading pip. Now runs 'python -m pip install --upgrade pip'
before auditing — removes the vulnerable version outright instead of
suppressing it.
pip 26.1.2 also fixes CVE-2026-3219 and CVE-2026-6357 (both were pip vulns, per
pip-audit attributing them to the pip package), so the two now-stale
--ignore-vuln entries are removed — stale security ignores mask the exact CVEs
they name if those reappear elsewhere.
Verified in a clean reproduction of the job env (fresh venv, upgrade pip, pip
install -e ., pip-audit --skip-editable with NO ignores): 'No known
vulnerabilities found', exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final straggler: memory scored 98% (diagnostics 55% = 9 pyright errors) in CI
while 100% locally. Proven cause: the diagnostics standard runs pyright over
every branch; memory's handlers import chromadb/numpy at module level. These
are declared in the 'memory' optional-dependencies group, NOT 'dev' — and the
audit job installed only '.[dev]', so pyright flagged them unresolved
(reportMissingImports=error) → 9 false errors. My local .venv happens to have
chromadb, which is why local audits read 100%.
Fix: audit job installs '.[dev,memory]'. pyright now resolves memory's real,
declared deps and the standard measures actual type-correctness (and matches a
local audit). api imports openai (llm extra) but guards it lazily, so it stays
100% without that extra — only memory needed this.
12/13 were already green after the readme check-ignore fix; this clears the
13th.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last 1%: 7 branches scored 99% in CI while 100% locally. Root cause proven
by reproducing CI's exact path (tracked-only tree + real .git): .gitignore
dir-only patterns (trailing slash — logs/, **/*_json/, .trinity/) do NOT match
via 'git check-ignore <bare-path>' when the path is absent from disk (clean
checkout), because git cannot infer 'directory' to apply a dir-only pattern.
The working tree has those dirs on disk, so it matched there — the exact
working-tree-vs-clean-checkout divergence.
_is_gitignored now also tests the trailing-slash form; all 7 readme failures
(cli_json/logs/artifacts/.trinity/ etc flagged 'missing on disk') clear.
Regression test builds a real git repo with dir-only patterns + non-existent
paths. CI gate also now prints failing standards + check messages (says WHY).
Verified: clean tree w/ real .git 13/13 100%; working tree 13/13 100%; seedgo
1053 tests green; pyright 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>