Skip to content

v2.8.29

Latest

Choose a tag to compare

@github-actions github-actions released this 27 Oct 10:28
v2.8.29
1226d85

[2026-10-27] — v2.8.29: the new-project system after Vera-Studio's day (AGENTS.md managed, the gitignore template and merge, updateignore, doctor's shadow and coverage rows), the watchdog per seat, a manager writes in its citizens, Codex wake lanes and parity

Added

  • aipass: aipass init, new and adopt seed .aipass/.gitignore (a whitelist, so the scaffold manifest and .aipass/.backup/ are never committed) and a root .updateignore holding only its header, the owner's file for what update must never touch.

  • aipass: aipass init update merges a project's .gitignore line by line: template lines the file lacks are appended under one dated comment, no owner line is removed or reordered, and --dry-run prints the lines. It seeds a missing .aipass/.gitignore and .updateignore without rewriting existing ones (Vera-Studio's dry run: four lines to add).

  • aipass: a new project's .claude/settings.local.json carries autoCompactWindow: 350000, the manager-seat default that needed a hand edit through the settings gate; existing files are never given it.

  • aipass: aipass doctor Structure row gitignore coverage, read-only: runtime files on disk that git tracks (.chroma, docs.local, dropbox, *_json, .spawn, the registry json) get one FAIL per folder with drone @git untrack as the fix; this is the check that would have caught Vera-Studio's 144 tracked files.

  • aipass: aipass doctor Structure row instruction shadow FAILs a live CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md beside an AGENTS.md (Claude Code reads AGENTS.md only when no CLAUDE file sits in cwd or above, so the project's AGENTS.md was never read: Vera-Studio and four other scaffolded projects, all passing doctor), says whether the two texts match, and --fix renames it to (disabled): never a copy, delete or overwrite, never under $HOME, never through a .claude symlinked outside the root (structure_scanner.py 1.2.0, doctor.py 1.10.0, docs/doctor.md; ten tests, 1411 green).

  • hooks: a project's manager writes inside its own citizens' branches (modules/write_ownership.py 2.5.0, docs/edit_gate.md; the owner's ruling 2026-10-26, the same shape as #808 for git). @vera was refused Cross-branch write blocked: vera cannot write to writer on both lanes because the manager exception covered only files outside every branch row. A non-AIPass project's manager row now writes its whole project; a citizen into a sibling citizen's row is still refused, crossings between projects are still refused, and inside AIPass the verified admin seat stays the only exception. One new test, two pins reversed, 3/3 mutants red, 2781 green.

  • hooks: navmap_cap (prompt/navmap.handle_edit, PostToolUse) says at edit time when .aipass/tier1_navmap.md is over its 8,000-char cap, with the count, instead of the injection cutting it silently near 10,000.

  • hooks: the project template (.aipass/project_hooks.json) carries culture, session_pointer, settings_gate, settings_backup and navmap_cap, so an external seat gets the culture file, a resume pointer for BAUD, the provider-settings gate and the navmap warning. compass_recall, auto_process, comment_budget and presence_gate stay host-only, reasons in src/aipass/hooks/docs/project_config.md. Existing projects take the entries through aipass init update.

  • drone: drone @git push: a project's manager pushes its own repo's checked-out branch to origin, never forced, no arguments. In AIPass it refuses and names dev-pr; push --repo stays the admin door.

  • drone: drone @git issue create|comment --repo AIOSAI/AIPass from any seat in any project, the body signed with the seat and project (--body only; --body-file, --editor and --web refuse there). Issues are the contribution channel; close, edit and delete stay owner-tier.

  • drone: git writes are fenced by the session, not the cwd: owner and ci-cure verbs refuse when the running Claude Code session started outside the project (its transcript sits under the launch directory, which a cd cannot move), unless the seat is admin. Gate and door logs carry the session id and where it started beside the seat.

  • hooks: drone @hooks parity [--branch @canary|<path>] [--lane A,..,F] [--provider claude,codex] [--json] runs the same scripted prompts headless on Claude Code and on Codex from one branch's cwd and compares what the hooks did, row by row: the twelve UserPromptSubmit handlers, SessionStart, the four gates (cross-branch write, raw git, raw rm, new test file), PostToolUse, Stop and SubagentStop, the over-cap .trinity edit. The oracle is logs/engine.jsonl: every per-hook line now carries the payload's session id, so a session's whole hook record is read back by the id each CLI prints. Accepted differences (culture native on Claude, the sub-agent mechanism) are marked, never counted; exit 1 on a real difference or a session that left no record, so the merge train can gate on it. First live run on canary: 32 rows, all same or accepted. docs/provider_parity.md.

  • daemon: a job's wake block names the provider next to the model: "wake": {"fresh": true, "model": "gpt-6-sol", "provider": "codex"}. Absent is claude, wake.py's own default, never a second copy; a value that is not claude or codex is refused at discovery by owner, job id and value. The scheduler pins both, the owner's ruling (discovery.py 2.6.0, run.py 1.9.0, rotation.py 1.4.0, job_reference.py 1.1.0, docs/schedule_contract.md; six pins in existing test files, six mutants red; proven live by a one-off canary job that woke on Codex, register row 371e953d).

  • aipass: the handoff's end choice is Claude Code or Codex, and the provider does not matter: choosing codex launches codex --dangerously-bypass-approvals-and-sandbox (the posture the rooms, the /codex skill and dispatch use, hooks being the gates) and the way back is codex resume --last; Claude's launch and breadcrumb are unchanged. aipass doctor gains four read-only Codex rows after codex hooks: the binary and its version, codex login status, the configured model checked against codex debug models for this login, and the four config pins (memories off, plan mode off three ways), each missing pin's fix being the exact TOML line to add by hand; --fix never touches config.toml, and a pin proves its bytes unchanged (handoff_platform 1.1.0, _doctor_wire.py 1.4.0, provider_wire.py 1.7.0, doctor.py 1.8.0, docs/two_clis.md; 1382 green, eight mutants red).

  • aipass, hooks: Codex hooks move to the user level, so a Codex seat in any project runs under the same 24 hooks as inside AIPass. @hooks measured Codex 0.164: $CODEX_HOME/hooks.json (else ~/.codex/hooks.json) fires from every cwd; the repo-level .codex/hooks.json loads beside it with no dedupe, so both files together fire every hook twice; trust is keyed <file>:<event>:<group>:<index> on content with no cwd, so one trust covers every project; untrusted rows skip silently. The writer now targets the user-level file and merges rather than replaces: AIPass rows (the ones running the Codex bridge) are replaced wherever they sit and placed first in each event, every foreign row and top-level key is kept behind them, backup and receipt as before. The tracked repo .codex/hooks.json leaves the tree. Doctor gains codex trust (a trusted_hash under each AIPass key in config.toml, else WARN naming the one key to press) and codex double-fire (FAIL while a repo-level file still holds bridge rows; --fix leaves the project's file). The bridge is unchanged (provider_wire.py 1.8.0, _doctor_wire.py 1.5.0, tests/test_provider_wire.py 1.9.0 with CODEX_HOME pinned to tmp for every test, docs/two_clis.md, docs/doctor.md; 1387 green, eight mutants red).

  • devpulse: the watchdog sees a Codex session. A branch whose session pointer names provider codex is watched through its one rollout under ~/.codex/sessions, re-read from the pointer on each refresh so a /codex switch is followed; a Codex turn is in flight until the rollout's last line is an event_msg of type task_complete or turn_aborted (Codex writes no session-end record), an unparseable tail counts as in flight. The wire and the sign-in take this seat's session id from CLAUDE_CODE_SESSION_ID, else from a Codex pointer's thread id (agent.py 1.6.0, dispatches.py 2.1.0 seat_session_id, wire.py 2.5.0, watchdog.py 2.2.0, docs/watchdog.md; seven pins in the existing test files, one mutant red). ai_mail's door_session_pointer 1.1.0 exports codex_thread_file, CLAUDE, CODEX for it.

  • seedgo: host_portability arm G flags a stdlib import newer than pyproject's requires-python floor (tomllib on a 3.10 floor; zoneinfo, graphlib on older ones) unless the same try/if falls back to the backport (import tomli as tomllib); a sys.version_info gate alone is not an acquittal, it moves the degraded answer to the floor's lane without running it. The floor is read from the nearest pyproject.toml, none means the arm is off. It is the species behind PR #811's 3.10-only red: fourteen aipass Codex tests whose degraded except branch no newer seat ever ran. Three pins red-first on the provider_wire reduction, five mutants red, fleet 0 live rows after seedgo's own cure (host_portability_check.py 1.5.0, host_portability_content.py 1.4.0, host_portability.md, docs/aipass_standards.md; FPLAN-0707).

  • seedgo: host_coupled_oracle, a tests-only standard (branch level): a test that passes its own os.getpid()/getppid() to an args/cmdline/title reader, or pins a literal runner call_count == N on a function that both forks on platform and runs a subprocess, is host-coupled and fails. Born of PR #807's three clone-only reds (pytest-xdist retitles its workers; macOS reads args through ps). Two pins on reductions of the real tests, seven mutants red, fleet 0 hits after hooks' cures.

Changed

  • aipass: a project's AGENTS.md is managed by manifest hash like the tier files (current / update / kept-local with the .aipass-new sidecar when the owner edited it), with an .updateignore row as the opt-out; it was a seed written once and never revisited, so tonight's template fix reached no existing project. A manifest from the seed era carries no trusted hash, so the first update never overwrites an edit an older update adopted (scaffold_manifest.py 1.1.0, bootstrap.py 2.5.0).
  • aipass: a new project gets a project navmap stub (its name, its citizens from the registry, the drone basics) in place of the host's roster map, which described AIPass's citizens in every external project. An unedited stub follows the roster on update; a host map or the project's own map is kept and the stub offered as .aipass-new.
  • spawn: drone @spawn update reads the project-root .updateignore as well as the branch's, matching root patterns against the project-relative path on the gitignore model (src/vera_studio/*/docs.local/ protects every citizen, a slashless name matches at any depth), so one file at a project's root protects all its citizens; the branch-local file stays as an optional extra and either one protects. A wildcard directory pattern now also withholds the bare directory row; an unreadable root file refuses before any write (update_ops.py 2.5.0, update_ignore.py 1.2.0, docs/update_engine.md; five new tests in the existing file, 3/3 mutants red, 1127 green).
  • prompts: .aipass/tier1_navmap.md trimmed under its 8,000-char cap (it was 9,422, cut silently at injection); the "Finding your way" section went, the kernel already says it; the settings line names the Codex files beside Claude's.
  • README: the install line says Codex as a choice (a second CLI, each agent runs on either, per task), no longer "optional", matching aipass.ai.
  • ai_mail: managers wake on Codex in the same lanes as on Claude: headless in the scheduled and admin lanes, and through a tmux session running codex --dangerously-bypass-approvals-and-sandbox on a @daemon self-wake. The Claude-only refusal of --provider codex for a citizen_class: manager is gone; the owner's ruling, after Vera's first Codex release post needed a hand-opened pane (wake.py 3.8.0, dispatch.py 3.7.0, docs/wake_pipeline.md, tests/test_wake.py 1.5.0: one refusal pin becomes four cases, two mutants red).

Fixed

  • seedgo: the provider hooks snapshot fixture (tests/fixtures/provider_hooks_snapshot.json) carries the Claude SessionStart:culture row the provider manifest gained, so the six test_hooks_snapshot reds are green.
  • hooks: README.md back under seedgo's 10,000-char README cap (was 10,099, the ratchet fleet walk red): seven Documentation-table descriptions shortened, wording only.
  • devpulse: the watchdog wire's identity primitives are public names (stdout_target, cmdline, looks_like_ours, session_dir_of in wire.py 2.7.1): the tests that prove them against a real process reached them as private names, seedgo's through_the_command row on the pre-push audit; no behaviour change, test_watchdog_wire.py 2.4.1 follows.
  • aipass: the Codex config reads (doctor's model and pin rows, set_codex_pins behind setup.sh and doctor --fix) fall back to the tomli package where tomllib is absent, the structure scanner's own shape, so Python 3.10 reads and pins config.toml like 3.11+; before, 3.10 answered "needs Python 3.11+" on every row and skipped the pins, and the fourteen Codex tests were red on CI's 3.10 lane (PR #811 run 40477098528). Without either parser the rows still say so, naming tomli now (provider_wire.py 1.9.1).
  • seedgo: the test inventory reads pyproject.toml on Python 3.10 through tomli instead of falling back to pytest's defaults on a sys.version_info gate; without either parser the note names both (test_inventory/collection.py 1.0.3, arm G's one fleet hit).
  • ci: the standards gate's expected count moves 83 → 84 for host_coupled_oracle, which landed without moving EXPECTED_STANDARDS in .github/scripts/seedgo_audit.py; the tripwire fired on the first branch of the local pre-push run and would have been the standards job's red.
  • aipass: the project .gitignore template covers the runtime state spawn and the services write (.chroma/, docs.local/, dropbox/, artifacts/, tools/ with a negation example, **/*_json/, .spawn/, .backup/, the registry and roots json, .env, local plans, WAL sidecars, .claude/worktrees/); Vera-Studio had 144 of one installation's files tracked on the old template (scaffold_content.py 1.4.0).
  • aipass: aipass init update retires a live CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md beside AGENTS.md to (disabled) with doctor's shadow fix and its refusals, and retires the pre-2.8.20 .claude/hooks/*.py scripts the manifest recorded while they are unedited.
  • aipass: aipass init <target> --dry-run took the flag as the project name and scaffolded a --DRY-RUN_REGISTRY.json; flags are refused before anything is written.
  • devpulse: arming the watchdog wire takes over only this seat's older wires; a wire registered by another seat (a project manager's, metadata.seat) is left alive and named on stderr, where before every live wire in the registry was SIGTERMed and the other manager's completions went unreported. Entries without a seat (pre-2.6.0) are taken over as before (wire.py 2.6.0).
  • devpulse: the watchdog's seat is the sealed owner of the caller's project, read through AIPASS_CALLER_CWD, where before drone's cwd switch made every seat @devpulse: @vera's wire delivered devpulse's completions, took devpulse's wire over as a same-seat arm, and watchdog status from her seat counted devpulse's dispatches as hers (#810). Each seat now drains the feed through its own cursor files (devpulse_json/<seat>.feed_cursor.json, same for the dead cursor), so one manager's drain never marks another's completions as seen; status lists the caller's seat's dispatches only and names the seat on each wire row (dispatches.py, feed.py, presenter.py, wire.py 2.7.0).
  • memory: drone @memory config get|set @branch resolves citizens of declared roots (@vera), so BAUD's settings panel saves for a project citizen; process-plans vectorizes every visible project's .backup/processed_plans (host, resident projects, declared roots), tags each chunk with its project, and search shows the project under the hit.
  • flow: a closed plan archives into its own project's .backup/processed_plans, never the host's (an external's plan history had landed under $AIPASS_HOME/.backup); restore looks there too.
  • flow: drone @flow list shows the caller's project's plans; list all from the host seat still lists every project. Plan numbers stay global.
  • aipass: the project AGENTS.md template (.aipass/project_AGENTS.md) and its generator follow the host startup protocol: memories read one file per call, no README read at startup, dashboard refresh and read, and the todo pad rules. It had drifted; a project scaffolded from it inherited the old startup. AGENTS.md is a seed file, so init update never rewrites an existing copy; only init, new and adopt write the new text.
  • hooks: Claude Code seats in external projects get the AIPass culture file at SessionStart (the Claude manifest has the SessionStart:culture row now); inside AIPASS_HOME Claude reads .claude/AGENTS.md natively and the hook stays silent, so nothing is injected twice.
  • drone: drone @git commit --all no longer dies with E902 in a project with no root tests/, and no longer rewrites files before refusing: the lint gate runs first over the dirs that exist, then ruff check --fix and ruff format touch only the changed files and the result names every file they rewrote.
  • aipass: projects get the provider switch. init, new and adopt write /codex (.claude/commands/codex.md) and $claude (.codex/skills/claude/SKILL.md) as managed scaffold files, init update adds them to older projects and keeps edited copies, and doctor's provider switch row names a missing one, which --fix writes. Before this the pair lived only in the AIPass tree, so no external project could switch CLI.
  • commands, skills: the switch files (/codex in .claude/commands/codex.md, $claude in .codex/skills/claude/SKILL.md) point their prep step at .agents/skills/prep/SKILL.md, where prep lives since the shared skills folder; they named .claude/commands/prep.md and .codex/skills/prep/SKILL.md, neither of which exists.
  • aipass, setup.sh: a fresh install with codex on PATH installs the Codex hooks again. setup.sh had looked for the repo's .codex/hooks.json, retired, and skipped the Codex step, leaving a new Codex seat ungated; the block it would have run also rewrote ~/.codex/config.toml wholesale with deprecated keys and a default model. Now setup.sh runs provider_wire.refresh_codex(), the same plan aipass doctor --fix applies: cli.codex merged into the user-level $CODEX_HOME/hooks.json with the owner's own rows kept, then the four Codex pins (memories, collaboration_modes, include_collaboration_mode_instructions, tools.experimental_request_user_input.enabled, all false) set one line each in config.toml, every other line kept, after a backup and with a receipt. A config.toml the line edit cannot set safely (a dotted key, an inline table) is refused and left untouched. refresh_provider_hooks() is Claude-only now. The install summary and the README name the user-level file and the one-time trust step.
  • hooks: drone @hooks verify reads Codex's hook rows from the user-level $CODEX_HOME/hooks.json (~/.codex/hooks.json), the only place they live since the repo .codex/hooks.json retired; it had kept reading the repo path and reported the Codex-only culture handler as unwired. A missing user-level file is now an ERROR naming the cure (aipass doctor --fix, then trust the hooks once in codex), not a skipped check, since a Codex seat without it runs ungated. One home rule in context_window.py (codex_home()) serves both codex_config_path() and the new codex_hooks_path(). The settings gate keeps protecting the retired repo path so the file never returns unnoticed beside the user-level one.
  • aipass: aipass doctor run from an external project's root no longer reports AIPASS_HOME split against that project. The terminal's tree is the registry root when that root is an AIPass tree (it holds src/aipass); in an external project it is the tree the project's .claude/settings.local.json (then settings.json) names in env.AIPASS_HOME. Two AIPass trees still fail the row (doctor.py 1.7.1, tests/test_doctor_home.py 1.2.0, three pins, two mutants red). Found by Vera-Studio's update to 2.8.28, the first run of the release ritual.
  • memory: rollover's trim re-reads the memory file before writing and removes only the archived entries, so an edit made while vectors were being stored (a new todo, a meta bump) is no longer overwritten by the image read at extraction. Found at 12:16 on devpulse's own pad: one Edit added a todo and pushed sessions to 11, the rollover it fired had read the file three seconds earlier, and its trimmed write put that older image back (extractor.py 0.9.0, orchestrator.py 1.4.1, tests/test_orchestrator_exec.py 1.1.0 pins the real sequence, two mutants red; docs/rollover_pipeline.md).
  • hooks: on Codex, an apply_patch that names no file is denied with that reason and logged at WARNING; it is a malformed call, not a broken engine, so it no longer raises HOOKS BROKEN. PostToolUse on such a patch runs nothing (bridges/codex.py 2.5.0, tests/test_codex_bridge.py 2.5.0, four mutants red).