v1.10.3 — Alignment Audit: Tool Surface 168 -> 112
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, theautopilotskill, and bothpipeline_gate.py/autopilot_auto_stop.pyhooks.pipeline_gatewas 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/autopilotroute — the only thing that could arm it — was removed first. Kept deliberately: thepipeline_stage_historytable and ORM (append-only history, writes stopped, nodrop_table),tasks.config['pipeline'], and the soft-retiredtask_typeparameter. - 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_verifierare kept — only the file-based heartbeat is gone.loop_statesandscheduled_tasksstop 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 intoagent_template_recommend(task_type=...)).team_createproduced teams with nokindkey, outside the reaper's exemptions, for which CC never creates a~/.claude/teams/<name>/directory — create one, lose one on the next sweep.agent_registerhad produced exactly 0 rows out of 2,396 agents. Theagents.trust_scorecolumn stays andauto_assignstill 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), andecosystem_recipes(merged intofind_skill(level=2, category="integration")). - Observability shells (
3faf81e) -prompt_version_list(nothing in the repo ever called/track, so/versionswas 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 only —api/guardrails.pyandInputGuardrailMiddlewareuntouched; 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 andget_file_hotspotsare 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)andtaskwall_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), andecosystem_pin_active/ecosystem_unpin/ecosystem_mark_no_value/ecosystem_clear_manual_statusmerged into a singleecosystem_repo_manual_status(repo_id, status, ...).ecosystem_mark_as_referenceis deliberately not merged — it drives the Stage-3 funnel viadeep_review_id, which is a different thing from a per-repo manual override. - Fourth hook registration surface (
82479f7) -src/aiteam/hooks/install.pyand theaiteam hooksCLI group, which wrote registrations into project-level.claude/settings.local.json(7 events, onlysend_event.py, stale matcher) and overwrote that file's wholehookskey. Alongside the global chain it madesend_eventfire twice per event. Independent-responsibility review found none. If you ever ranaiteam hooks install, that project's.claude/settings.local.jsonis still double-firing — see/os-hooksfor the cleanup prompt. scripts/install.py(a2893ed) - a no-op redirect since 2026-07-22 whoseHOOK_EVENTSwas 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-allexceptlet it through), theCORE_TOOLS/ADVANCED_TOOLSdead lists (85 lines naming 6 tools that never existed), and thecontinuous-modeskill directory.- Stale in-repo agent templates (
906bcc3) - the 22 copies under.claude/agents/; 16 still saidmodel: sonnetand none carrieddisallowedTools. 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_reminderwas writingpermissionDecision=allowon every PreToolUse call. That field is an optional stance that outranks the user's chosen permission mode, sodefault/plan/acceptEditswere 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 hooktimeoutin seconds (command default 600); the whole repo had been writing milliseconds, so3000/300000meant 50 minutes to 83 hours — timeout protection was effectively disabled. Re-based on measured runtimes (session_bootstrap0.20s -> 15,send_event0.08s -> 5,workflow_reminder0.09s -> 5,auto_install-> 180) acrosshooks.json,install.pyand 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 butdeps._auto_create_projects, which on every API start bulk-bound all unowned teams to whichever project matched the API process's ownos.getcwd(), falling back toexisting_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 fromteam.project_id. Now resolved per-team from the team's own members (Leader first), leaving teams unowned rather than guessing. Ships withscripts/repair_team_project_attribution.py(dry-run by default). - Tasks created through a team had no project (
c0a2e82) -task_runand three other entry points created tasks withproject_id = None, soGET /api/projects/{id}/task-wallnever saw them. Fixed at the single choke pointrepository.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 rowsqueued+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-writtenclaimed_byis 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-SQLisoformat()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, thentick()skipped the whole batch because those rows already existed, and the return value was swallowed by a bareexcept. Approvals produced a batch nobody could run. Now routed throughworker.dispatch_batch; a corrupt snapshot fails loudly with a 500 instead of being silently treated as empty. ecosystem_index_updatewas gated on emptied tables (fa1a09a) - hard validation againstdata_sources/scan_profilesblocked 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; failedghqueries returnquery_errorsinstead 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 pickedactive_teams[0], which in practice is the newest per-runworkflow-*team, so meeting / knowledge / activity tools all bound to the wrong team;inject_subagent_contextfell back to reading the parent session's first user message as if it were the dispatch prompt;send_eventmatched template names against custom member names;project_createaccepted a bare-prefix root path with no separator boundary (/Users/croncould claim/Users/cronus/...);delete_projectmissedscope='team'memories and cleaned events using a lazy subquery that was always empty by the time it ran; twodecision_logbranches compared prefixed tool names against bare literals, one of them against a tool name that never existed. team_briefingevents and project-scoped event filtering (cb8e147) - the old predicateentity_id IN team_idsmatched 0 of 229,110 event rows, so adding a filter literally would have emptied the briefing. A new_team_scope_clausecovers all four attribution shapes actually in use, which also repairedGET /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'shooks.jsonand disagreed on matchers.install.pygainsHOOK_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_SCRIPTSactively removes retired hooks left running on machines that installed earlier versions. scripts/update.pyaborted halfway on modern Pythons (a2893ed) -pip install -e .is refused on PEP 668 externally-managed interpreters (e.g. Homebrew python3.12), andcheck=Truekilled 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/shutdownexits viaos._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_recoverywas deaf on non-default ports (a2893ed) - the only hook with a hard-coded:8000; now readsapi_port.txt.- Ten tests had been red since
75dcb18(a9a2e5f) - the file lives intests/rather thantests/unit/, and every past "full suite green" sign-off had only runtests/unit. The acceptance standard is nowpytest tests.
Added
GET /api/agents/whoamiand OS identity injection (cb8e147) - a sub-agent can find its own OS agent id without a registration handshake:inject_subagent_contextinjects an identity block resolved fromcc_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_briefingsgains atagscolumn;briefing_addaccepts tags,briefing_listand the REST endpoint filter by project and tag, and the Dashboard shows tag badges with a filter bar.project_iddeliberately 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, notLIKE '%tag%', soreleasenever matchesrelease-candidate. - Rule B0.16b — pending decisions must be queued, not left in a report (
56bce86) - added to/api/system/rulesafter 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.pypinsinstall.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 thesrcside and missing twins were both silently invisible. - Repair scripts, dry-run by default -
scripts/reclaim_stale_shallow_claims.pyandscripts/repair_team_project_attribution.py. Both require--applyto write. task_rungainspriority/horizon/tags/assigned_to(a4f5e9c) - its docstring had been instructing callers to set priority and horizon, which the signature did not accept.event_listgainstype/source/entity_id/project_idfilters (a4f5e9c) - the underlying query always supported them; the tool exposed onlylimit.
Changed
- Hook registration surface unified at 11 events / 17 entries (
a2893ed) -send_eventkeeps 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 atrunningforever.workflow_remindernarrowed toAgent|Bash|Edit|Write|Workflow;deep_review_linkandmeeting_ecosystem_writebacknarrowed from*to their own tool names. failure_analysismemories 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_knowledgereads both scopes so/api/teams/{id}/knowledgestays whole across the boundary.prompt_effectivenessno 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_livenessretired (cb8e147) - it existed to follow CC'sTeamDelete, 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_teamstakes 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.mdrecipes rewritten off the retired tool names,docs/architecture.mdloop/description matched to its actual contents.
Docs - README screenshot generation (first release since the refresh)
- Both READMEs' screenshot sections fully regenerated (
845ddf0,21ea140, merged in5c7c19c) - 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), andagent-lanes(14 agents across teams). The English README now uses its own-enimage set rather than sharing the Chinese captures. - Every capture reshot to a fixed viewport (
21ea140) -fullPage=falseat 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.pybuilds a self-contained demo dataset, andstorage/connection.pygained anAITEAM_DB_PATHoverride (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 asession-<sid8>container team, with Leader drift corrected on the way in. The unconditionalexit(2)block in_check_agent_team_nameis 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 viaowner_session_idwhile CC v2.1.219's implicit team name is an internal id with no transcript, so it fell back tocreated_atand closed the team; teams created before the Leader registered could never get aproject_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=projectwrites from a directory that matches no OS project used to silently fall back to the globalsystembucket, 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, absolutesys.executablepaths make the chain work on Windows where thepython3token 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 is4ae1539above. - 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 -
SidebarInsetgainsmin-w-0so wide tables stop stretching the whole page past the viewport (c72b921). The README screenshot regeneration that shipped alongside it is described under Docs above.