Skip to content

v1.10.3 — Alignment Audit: Tool Surface 168 -> 112

Choose a tag to compare

@CronusL-1141 CronusL-1141 released this 30 Jul 04:20
· 160 commits to master since this release

An engineering-governance release: no new feature domain. A full OS-vs-Claude-Code alignment audit (10 parallel evidence-gathering agents, then 8.5 remediation batches) found that a large share of the tool surface had quietly stopped meaning anything — subsystems whose storage was empty for their entire lifetime, tools whose only effect was to create rows nothing reads, and hooks that fired on every single tool call to decide they had nothing to do. Every removal below is backed by production measurement, not by "looks unused". Net effect across the release: 296 files, +11,331 / -22,002 lines, MCP tool surface 168 -> 112.

Two user-visible behaviour changes worth calling out: permission prompts come back (the OS was silently auto-approving five tool classes), and tasks created through a team now show up on that project's task wall.

Removed - tool surface 168 -> 112 (57 retired, 1 added by merge)

  • Pipeline domain, entirely (792e8ef) - pipeline_create / pipeline_advance / pipeline_status, src/aiteam/pipeline/ (9 files), loop/pipeline.py, the pipeline REST routes, the autopilot skill, and both pipeline_gate.py / autopilot_auto_stop.py hooks. pipeline_gate was registered on the matcher-less PreToolUse and PostToolUse groups: two extra processes per tool call, guarding an allowlist frozen in 2026-06 that would have blocked CC's current native tools the moment autopilot was switched on. The /pipeline/v2/autopilot route — the only thing that could arm it — was removed first. Kept deliberately: the pipeline_stage_history table and ORM (append-only history, writes stopped, no drop_table), tasks.config['pipeline'], and the soft-retired task_type parameter.
  • Loop state machine, scheduler and heartbeat (5849317) - loop_start / loop_pause / loop_resume / loop_advance / loop_next_task / loop_review / loop_status, scheduler_create / scheduler_list / scheduler_pause / scheduler_delete, agent_heartbeat / watchdog_check. Measured: 14 scheduling tools touched 6 times in 21 days, all of them smoke tests; the loop machine did an unreachable DB round-trip against every active team every 60 seconds to produce 191 idle shells; the heartbeat directory was never created and could not have worked anyway, since CC subagents are one-shot processes that never poll. WatchdogChecker / WatchdogRunner / completion_verifier are kept — only the file-based heartbeat is gone. loop_states and scheduled_tasks stop being written but are not dropped.
  • Team and agent traps (cb8e147) - team_create (+ POST /api/teams), agent_register, agent_trust_scores / agent_trust_update, team_setup_guide (merged into agent_template_recommend(task_type=...)). team_create produced teams with no kind key, outside the reaper's exemptions, for which CC never creates a ~/.claude/teams/<name>/ directory — create one, lose one on the next sweep. agent_register had produced exactly 0 rows out of 2,396 agents. The agents.trust_score column stays and auto_assign still weights it; only the never-called scoring chain is gone.
  • Base domain (6cef48f) - pattern_record / pattern_search (the store was empty for its entire life, so the "past execution patterns" injection block was a guaranteed-blank API call on every dispatch), phase_create / phase_list, os_report_issue / os_resolve_issue (0 issues ever filed), send_notification (its webhook config file was never created, so the call could only fail), cross_project_send / cross_project_inbox, team_knowledge (MCP tool only — repository, REST endpoint and Dashboard all kept), git_auto_commit / git_create_pr / git_status_check (agents already have Bash; a three-layer git wrapper only narrows CC), and ecosystem_recipes (merged into find_skill(level=2, category="integration")).
  • Observability shells (3faf81e) - prompt_version_list (nothing in the repo ever called /track, so /versions was permanently empty and the Dashboard rendered "-" in three columns for every row), error_budget_status / error_budget_update (data directory empty for its entire 17-day life), guardrail_check / guardrail_check_payload (thin MCP wrappers onlyapi/guardrails.py and InputGuardrailMiddleware untouched; the real guardrail is enforced at the HTTP layer), file_lock_acquire / release / check / list (lock file measured as {} in every run). Hook-side edit-conflict detection and get_file_hotspots are kept — they read real edit events, not lock files.
  • Task domain (a4f5e9c) - task_decompose / task_subtasks (0 subtasks in the entire database), task_auto_match (there is no pool of idle agents to match against under CC's on-demand dispatch), what_if_analysis / task_compare, plus two merges: task_replay -> task_execution_trace(include_stats=True) and taskwall_view -> task_list_project(team_id=...). The four REST endpoints are kept.
  • Ecosystem (fa1a09a) - ecosystem_data_source_create / ecosystem_scan_profile_update (no-op stubs), and ecosystem_pin_active / ecosystem_unpin / ecosystem_mark_no_value / ecosystem_clear_manual_status merged into a single ecosystem_repo_manual_status(repo_id, status, ...). ecosystem_mark_as_reference is deliberately not merged — it drives the Stage-3 funnel via deep_review_id, which is a different thing from a per-repo manual override.
  • Fourth hook registration surface (82479f7) - src/aiteam/hooks/install.py and the aiteam hooks CLI group, which wrote registrations into project-level .claude/settings.local.json (7 events, only send_event.py, stale matcher) and overwrote that file's whole hooks key. Alongside the global chain it made send_event fire twice per event. Independent-responsibility review found none. If you ever ran aiteam hooks install, that project's .claude/settings.local.json is still double-firing — see /os-hooks for the cleanup prompt.
  • scripts/install.py (a2893ed) - a no-op redirect since 2026-07-22 whose HOOK_EVENTS was still a second, drifting registration table. Also removed: task_completed_gate (structurally dead — CC task ids are integers, OS ids are UUIDs, so /api/tasks/<int> always 404'd and a catch-all except let it through), the CORE_TOOLS / ADVANCED_TOOLS dead lists (85 lines naming 6 tools that never existed), and the continuous-mode skill directory.
  • Stale in-repo agent templates (906bcc3) - the 22 copies under .claude/agents/; 16 still said model: sonnet and none carried disallowedTools. CC resolves templates project > user > plugin, so this directory shadowed the authoritative plugin versions inside this repo. Removing it falls back to the complete 25-template opus set.

Fixed

  • Permission decisions handed back to CC (6899f3c) - workflow_reminder was writing permissionDecision=allow on every PreToolUse call. That field is an optional stance that outranks the user's chosen permission mode, so default / plan / acceptEdits were all overridden and the permission prompt was silenced for Agent / Bash / Edit / Write / Workflow — 43,605 calls over 30 days. Prompts come back with this release.
  • Hook timeouts were off by 1000x (6899f3c) - CC documents hook timeout in seconds (command default 600); the whole repo had been writing milliseconds, so 3000 / 300000 meant 50 minutes to 83 hours — timeout protection was effectively disabled. Re-based on measured runtimes (session_bootstrap 0.20s -> 15, send_event 0.08s -> 5, workflow_reminder 0.09s -> 5, auto_install -> 180) across hooks.json, install.py and the example.
  • Project attribution race (4ae1539) - agents working in one project were recorded under another, with the wrong path rendered into their system prompt. The root cause was not the SubagentStart hook but deps._auto_create_projects, which on every API start bulk-bound all unowned teams to whichever project matched the API process's own os.getcwd(), falling back to existing_projects[0]. The API process cwd is shared global state. Both symptoms had one root, since the agent row and the prompt's {project_path} are both inherited from team.project_id. Now resolved per-team from the team's own members (Leader first), leaving teams unowned rather than guessing. Ships with scripts/repair_team_project_attribution.py (dry-run by default).
  • Tasks created through a team had no project (c0a2e82) - task_run and three other entry points created tasks with project_id = None, so GET /api/projects/{id}/task-wall never saw them. Fixed at the single choke point repository.create_task (explicit argument > owning team's project > request scope) rather than patching four call sites.
  • Ecosystem queue deadlocked for 17 days (fa1a09a) - measured in production: 74 rows queued+claimed, 0 claimable. The claim was a permanent reservation with no expiry, so a single dead sub-agent hid a row from every claimant forever. The pre-written claimed_by is deliberate (it prevents tick-dispatch and pull-claim from grabbing the same row) and was kept; it became a lease instead — STALE_CLAIM_TTL_SECONDS=3600, chosen above both the 600s Stage-0 timeout and the 45-minute deep-review watchdog so it can never steal work from a live worker. Also fixed a latent format bug: raw-SQL isoformat() timestamps were string-incomparable with ORM-written ones.
  • Shallow-scan approval dispatched nothing (fa1a09a) - the approve endpoint hand-rolled review rows without a dispatch prompt, then tick() skipped the whole batch because those rows already existed, and the return value was swallowed by a bare except. Approvals produced a batch nobody could run. Now routed through worker.dispatch_batch; a corrupt snapshot fails loudly with a 500 instead of being silently treated as empty.
  • ecosystem_index_update was gated on emptied tables (fa1a09a) - hard validation against data_sources / scan_profiles blocked the tool and its four downstream diff tools. Configuration now reads from settings, and the contradictory thresholds in step 3b (1000) vs step 4 (5000) were reconciled; failed gh queries return query_errors instead of being swallowed.
  • Seven "wrong object" bugs (2238315) - all one disease: identifying an object by a key from a different, non-authoritative source. Empty-argument team resolution silently picked active_teams[0], which in practice is the newest per-run workflow-* team, so meeting / knowledge / activity tools all bound to the wrong team; inject_subagent_context fell back to reading the parent session's first user message as if it were the dispatch prompt; send_event matched template names against custom member names; project_create accepted a bare-prefix root path with no separator boundary (/Users/cron could claim /Users/cronus/...); delete_project missed scope='team' memories and cleaned events using a lazy subquery that was always empty by the time it ran; two decision_log branches compared prefixed tool names against bare literals, one of them against a tool name that never existed.
  • team_briefing events and project-scoped event filtering (cb8e147) - the old predicate entity_id IN team_ids matched 0 of 229,110 event rows, so adding a filter literally would have emptied the briefing. A new _team_scope_clause covers all four attribution shapes actually in use, which also repaired GET /api/events?project_id= — silently empty until now.
  • Two installation paths produced two different systems (a2893ed) - the source installer registered 4 fewer events than the plugin's hooks.json and disagreed on matchers. install.py gains HOOK_SURFACE (11 events / 17 entries) as the single source of truth and switched from append-only to "clear our own entries, then rebuild" — otherwise matcher and timeout changes could never land. Our entries are identified by an explicit script-name allowlist, never by path substring, so third-party hooks in the same directory survive (verified). RETIRED_HOOK_SCRIPTS actively removes retired hooks left running on machines that installed earlier versions.
  • scripts/update.py aborted halfway on modern Pythons (a2893ed) - pip install -e . is refused on PEP 668 externally-managed interpreters (e.g. Homebrew python3.12), and check=True killed the update at step 2 of 7, so hooks, skills, commands and settings were never refreshed. pip failure is now non-fatal.
  • Governance lease was never released on shutdown (3e7add0) - /api/system/shutdown exits via os._exit(0), which skips the lifespan teardown where the release lived. Moved into the exit path itself (best-effort, never blocks exit); TTL remains the backstop. Measured end to end: the successor takes over immediately instead of after a 180-second governance vacuum.
  • permission_denied_recovery was deaf on non-default ports (a2893ed) - the only hook with a hard-coded :8000; now reads api_port.txt.
  • Ten tests had been red since 75dcb18 (a9a2e5f) - the file lives in tests/ rather than tests/unit/, and every past "full suite green" sign-off had only run tests/unit. The acceptance standard is now pytest tests.

Added

  • GET /api/agents/whoami and OS identity injection (cb8e147) - a sub-agent can find its own OS agent id without a registration handshake: inject_subagent_context injects an identity block resolved from cc_tool_use_id, and the endpoint offers a three-level server-side lookup (cc id -> session+name -> name) for when enrolment hasn't landed yet.
  • Briefing tags and filtering (56bce86) - leader_briefings gains a tags column; briefing_add accepts tags, briefing_list and the REST endpoint filter by project and tag, and the Dashboard shows tag badges with a filter bar. project_id deliberately defaults to unset = show everything: a decision inbox must not hide anything by default, and rows created before 2026-07-27 carry no project stamp. Tag comparison is whole-value, not LIKE '%tag%', so release never matches release-candidate.
  • Rule B0.16b — pending decisions must be queued, not left in a report (56bce86) - added to /api/system/rules after batch 7+8 wrote its open questions into a completion report while the briefing queue stayed empty, so the user never actually received them.
  • Invariant I8 (a2893ed) - scripts/check_hook_surface.py pins install.py <-> hooks.json <-> both READMEs together. I1 also became a bidirectional set comparison with an explicit allowlist; the old version only walked the plugin side, so files added only on the src side and missing twins were both silently invisible.
  • Repair scripts, dry-run by default - scripts/reclaim_stale_shallow_claims.py and scripts/repair_team_project_attribution.py. Both require --apply to write.
  • task_run gains priority / horizon / tags / assigned_to (a4f5e9c) - its docstring had been instructing callers to set priority and horizon, which the signature did not accept.
  • event_list gains type / source / entity_id / project_id filters (a4f5e9c) - the underlying query always supported them; the tool exposed only limit.

Changed

  • Hook registration surface unified at 11 events / 17 entries (a2893ed) - send_event keeps the full * matcher (telemetry must stay complete) with cost handled by an internal inert-tool early return that is symmetric across Pre/Post — dropping only the Post side would pin every started span at running forever. workflow_reminder narrowed to Agent|Bash|Edit|Write|Workflow; deep_review_link and meeting_ecosystem_writeback narrowed from * to their own tool names.
  • failure_analysis memories now belong to the project, not the team (a4f5e9c) - teams are session- or workflow-scoped and short-lived, so a lesson died with its team (125 such orphans already existed). Historical rows are not migrated; list_team_knowledge reads both scopes so /api/teams/{id}/knowledge stays whole across the boundary.
  • prompt_effectiveness no longer does N+1 queries (a4f5e9c) - it looped three queries per team (762 round-trips across 254 teams) and pulled up to 2,000 activity rows into Python just to count them; now two aggregate SQL queries.
  • state_reaper._check_team_liveness retired (cb8e147) - it existed to follow CC's TeamDelete, which no longer exists. Of 254 measured teams (workflow 168 / session 80 / no kind 6) the first two categories were already exempt, so the branch could only ever reach legacy teams: zero benefit, unchanged mis-closure risk. _check_stale_teams takes over, and its "empty team" path gained the meeting grace period it had been missing.
  • Documentation trued up to measurement - both READMEs: tool groups 21 -> 16 (the table still listed trust / error_budget / file_lock / git / guardrails, all gone with their modules), tool modules 21 -> 16, hook lifecycle events 12 -> 11, REST endpoints 199, machine-checked invariants 9. Retired-subsystem bullets rewritten rather than deleted, so the roadmap records what was retired and why. docs/ecosystem-recipes.md recipes rewritten off the retired tool names, docs/architecture.md loop/ description matched to its actual contents.

Docs - README screenshot generation (first release since the refresh)

  • Both READMEs' screenshot sections fully regenerated (845ddf0, 21ea140, merged in 5c7c19c) - 12 pages x zh/en = 24 viewport captures, plus the shared auto-wake demo, for 25 tracked images and 13 local image references per language. Four views are new to the README: workflows (run cards with a live running workflow), workflow-detail (phase swim lane plus the per-agent telemetry table, including a failed contract check rendered in red), project-detail (Leader context watermark and the worktree uncommitted-work notice), and agent-lanes (14 agents across teams). The English README now uses its own -en image set rather than sharing the Chinese captures.
  • Every capture reshot to a fixed viewport (21ea140) - fullPage=false at 1440x900 throughout, replacing the previous full-page renders; one of them had been 10,963px tall, which no README reader could usefully view.
  • Screenshots are generated from a neutral seeded database (845ddf0) - scripts/demo_seed.py builds a self-contained demo dataset, and storage/connection.py gained an AITEAM_DB_PATH override (with 4 unit tests) so capture runs point at that seed instead of a real working database. No production data reaches the published images.
  • Dashboard i18n completed for the newly-captured views (845ddf0) - 21 keys added in both languages across the Settings model-governance card, project detail, teams, global search and the model selector, so the English captures are genuinely English rather than partially untranslated.
  • Orphan screenshots purged (4dde89f, d41d55e) - 7 zero-reference images removed, including a 2.5 MB mobile capture and a 42,859px-tall full-page render left over from June. docs/screenshots/ now has zero orphans: all 25 tracked files are referenced, and every reference resolves (verified mechanically at release time).

Also in this release (landed after the v1.10.2 tag, previously unreleased)

  • Agent auto-enrolment, exemption blind spot retired (75dcb18) - direct agent dispatch is fully unblocked and the team system moved from a mandatory precondition to an automatic backstop: an agent with no team is no longer skipped but enrolled into a session-<sid8> container team, with Leader drift corrected on the way in. The unconditional exit(2) block in _check_agent_team_name is gone; the cross-project dispatch guard from the 2026-05-08 incident stays.
  • "Dead team, live people" triple misdiagnosis (6a5f63e) - four agents ran in parallel while the OS showed nothing. Three layers stacked: the reaper probed liveness via owner_session_id while CC v2.1.219's implicit team name is an internal id with no transcript, so it fell back to created_at and closed the team; teams created before the Leader registered could never get a project_id; and the frontend filters on active teams. Liveness now folds in members' last-activity time, and a contradictory state (completed team with busy members) self-heals.
  • Memory isolation for unregistered directories (29a366d, f48b808) - scope=project writes from a directory that matches no OS project used to silently fall back to the global system bucket, broadcasting one directory's project memory to every session. They now derive a per-directory bucket server-side (the fingerprint formula deliberately lives only on the server, never copied into hook files); with no cwd at all the request is refused rather than globalised.
  • Plugin installation self-healing (ce64d8a, 8559348, ee9cf09, 46c9bdc, 96dc6f7) - version comparison replaces "import succeeded, we're done", a first-run progress card replaces silence, absolute sys.executable paths make the chain work on Windows where the python3 token cannot be resolved, the standalone install path distributes skills and commands, and the Python version gate moved to module top level (a PEP 604 annotation was raising at def time on 3.9, making the guard itself unreachable — reported via community PR #4).
  • Nagging-reminder governance (6de9695) - five reminder sites made low-frequency and silenceable, on the principle that systematically-ignored reminders are a pure token and attention tax. Safety guardrails (S1-S3, rm, force push) were not touched.
  • Team project reassignment (c47cc70) and the cwd-fallback ban for team binding (e812de4) - the only way to correct an attribution the system guessed wrong, plus the hook-side half of the fix whose startup-side counterpart is 4ae1539 above.
  • Concurrency and lint hardening - teams find-or-create race no longer swallows the error (7595427), the reaper reclaims adopted-but-terminal workflow session shells (32a2ab7), ruff reaches zero across the repo and becomes invariant I7 after two red CI runs (d64c635, 702f89f).
  • Dashboard - SidebarInset gains min-w-0 so wide tables stop stretching the whole page past the viewport (c72b921). The README screenshot regeneration that shipped alongside it is described under Docs above.