Skip to content

[feat] One descriptor per tool, and one resolver for where teamai writes and where the tool reads #1221

Open

Description

@SaulMoro

Problem

teamai writes a file, doctor reports ✔ delivered, and the tool never reads the file. #946 found this for rules in most tools. #1197 was the latest case: Cursor reads ~/.cursor/rules only by walking up from the workspace, so a workspace outside $HOME got no user rules. #1198 fixed it for Cursor, through its session-start hook. Nothing stops the next tool from having the same gap.

Most delivery checks compare the file teamai wrote with what teamai renders. A few also ask where the tool reads: relocated tool roots (toolRootRejection), CodeBuddy's first-existing MCP file (shadowedMcpRecords) and Cursor's session-start fallback (#1198). Each is written for one tool, so the next tool with the same gap gets no check. The code makes that gap likely:

To size the gap I built a matrix, at the end of this issue: 19 tools, user, workspace and self scope, and the conditions that change a cell.

Proposal

The goal is two things at once: a test fails when teamai writes where a tool does not read, and adding a tool, or filling in a feature for one teamai already supports, means choosing from what exists.

flowchart LR
    subgraph callers ["callers"]
        pull["pull, uninstall<br/>(writers)"]
        doctor["doctor"]
        misc["models switch, status,<br/>local agent"]
        test["matrix test"]:::added
    end
    subgraph core ["one module"]
        locate["locate(tool, resource, install, host)<br/>→ writes, reads, gates"]:::added
        desc["TOOLS descriptor<br/>home, skillLoad, channels, support"]:::added
    end
    subgraph adapters ["existing adapters"]
        rf["RULE_FORMATS"]
        hf["hook formats and plugins"]
        mf["MCP formats"]
        af["renderForTool"]
    end
    host{{"Host<br/>realHost() or a test host (path.win32)"}}:::added
    page["docs/guide/tool-support.md<br/>en + zh-CN"]:::added
    old["per-tool resolvers today:<br/>skillsDirForTool, instructionTargetPath, resolveMcpTargets,<br/>getHermesHome, resolveDshHome, resolveToolSettingsPath, ..."]:::removed
    pull & doctor & misc & test --> locate
    locate --> desc
    locate --> host
    desc --> rf & hf & mf & af
    desc --> page
    pull -.-> old
    doctor -.-> old
    classDef added fill:#2f8f4f,stroke:#1f6b39,color:#fff;
    classDef removed fill:#a33a3a,stroke:#7a2a2a,color:#fff,stroke-dasharray:4 3;
Loading

Green is new. The red resolvers are deleted as each resource moves.

One descriptor per tool

Each tool gets one entry. The entry says where the tool's home is and which existing adapter each resource uses. It also records what the tool itself does, with evidence.

export const TOOLS = {
	cursor: {
		home: { dir: '.cursor' },
		skillLoad: { calls: [] }, // Cursor loads a skill by reading SKILL.md
		rules: { channel: 'file', format: CURSOR_MDC_FORMAT },
		blocks: { user: 'file', workspace: 'file' },
		hooks: { channel: 'settings', format: 'cursor' },
		mcp: { format: 'claude' },
		agents: { format: 'cursor' },
		support: { /* tool-side facts per feature and scope, with evidence */ },
	},
	// one entry per tool
} satisfies Record<ToolId, ToolDescriptor>;
  • ToolId is the key set of the default toolPaths, lifted out of the zod default into a const. A default tool without an entry fails tsc. Custom tool ids in a team's toolPaths keep working as today.
  • The adapters already exist: RULE_FORMATS (8 formats), the hook formats and plugins, the MCP formats, renderForTool, and the instruction-block channels. The descriptor picks one per resource. A new adapter is needed only for a format that is new, as Trae's rule format was. A per-tool adapter class with one method per feature would make every new feature touch every tool, so this proposal does not use one.
  • "teamai builds it" is derived from the descriptor: a resource with a channel is built, and one without is not. support records only what the tool does: a status, an evidence class, a version, a note, and for a broken cell the issue that tracks it. A cell left out means unknown. The data cannot claim ✓ where teamai writes nothing.
  • Every default tool states each channel: an adapter, 'none' (JoyCode has no hooks, Pi and Hermes have no subagents), or 'unknown'. Leaving it out fails tsc, so a tool cannot fall back to Claude-shaped hooks or a silent MCP skip, and nobody has to invent an adapter for a feature the tool lacks.
  • skillLoad says how the tool's agent loads a skill: its dedicated skill tool (Skill, skill, read_skill), each with an evidence class (verified, source, docs, assumed), and a prompt mention that loads a skill with no tool call (Codex $name, Pi /skill:name). Reading a SKILL.md path also counts; classifyToolCall, which recall already runs, detects that for every tool, shell reads included. Tracking runs on the wildcard post-tool-use pass every tool already installs, and one skill counts once per turn however the agent loaded it. That replaces SKILL_TOOL_MATCHER and the per-tool Skill hook. Copilot, Pi, OMP, dsh, OpenCode 2.x and Codex record no usage today.

Filling in a feature becomes a descriptor edit. For example, Muse's workspace instruction blocks would use the session-hook channel Codex already uses.

One module resolves where teamai writes and the tool reads

export async function locate(tool: string, resource: Resource, install: Install, host: Host = realHost()): Promise<Location>;

type Install = { toolPaths; agents: { enabledAgents, disabledAgents }; toolRoots } & ({ scope: 'user' } | { scope: 'project'; projectRoot: string });
type Host = { platform; path: path.PlatformPath; home; env; toolHomes; cwd(): string; exists(path): Promise<boolean>; readJson(path) };
type Location = { own: string | null; shared: string | null; reads: Read[] | 'unknown'; gates: Gate[] };
type Read = { path; when: 'always' } | { path; when: 'workspaceUnder' | 'cwdOnly'; root };

Install is what the member's config decides: the team's toolPaths, the scope, self mode (repo.kind), the recorded tool roots, and the main checkout that self-mode hooks go to. Host is what the machine decides. Keeping them apart preserves today's rule that a recorded root wins over the current shell's env, while doctor can still compare the two.

A home moved by a variable is a machine fact. init and the first pull that sees the move record it in ~/.teamai/tool-homes.json, and Host.toolHomes carries it. The record wins over a shell without the variable, so a hook-run pull and a terminal pull agree. When a tool's skills directory moves, pull stops updating teamai's copies in the old one and says so once; it never deletes them, because a reader in another scope, shell or untracked project can't be seen from a pull. A2 adds the skills case (#1252). Self mode and the main checkout join Install with B4.

locate absorbs the tool home resolution (env vars, XDG, profiles, the Windows default), the per-tool toolPaths merge, the special cases (Hermes, OpenClaw, dsh, Copilot, CodeBuddy's MCP lookup, the local agent), and the read rules (walk-up, cwd only, git root). pull, uninstall, models switch, status, doctor and the tests all call it. Writing and reading share one home resolution, so the class "teamai writes ~/.config/opencode while OpenCode reads $XDG_CONFIG_HOME/opencode" goes away by construction.

Host has two implementations: realHost() (process.env, os, fs), the default, and literal values in tests. That is what lets a test on Linux check C:\Work with path.win32. It covers path resolution only. The other process.env reads stay as they are.

Tests through the same functions

  • Matrix test, every PR. It calls locate for each tool, resource and scope on darwin, linux and win32, with the cwd inside $HOME, outside it and in a project subdirectory, and with each env override set and unset. Each write must be in the tool's reads, or the cell must be marked broken with its issue. The expected values come from the matrix at the end, not from a run of locate. It takes seconds and downloads nothing.
  • Skill tool test. For each tool with skills, a skill call in that tool's payload shape goes through the dispatcher and records one usage event, credited to that tool.
  • Tool list test. ALL_SUPPORTED_TOOLS, ModelAgentSchema, LOCAL_WORKFLOW_TOOLS, SELF_MODE_AGENT_CHOICES and the alias tables are each a subset of ToolId that matches the descriptor's channel for that feature. KNOWN_AGENTS keeps its discovery-only entries. Its paths for default tools must match locate, except pi, omp and opencode, which keep the project path.

What the matrix test sees, for two cells from the findings:

host                     tool      resource  writes today                  tool reads                       result
linux, XDG unset         opencode  skills    ~/.config/opencode/skills     ~/.config/opencode/skills        pass
linux, XDG=/x            opencode  skills    ~/.config/opencode/skills     /x/opencode/skills               fail   ← A2 makes this pass
win32, cwd C:\Work       cursor    rules     ~\.cursor\rules               walk-up from C:\Work only        not reached, so the session-start hook adds them (#1198)

The matrix test checks paths only. Delivery that happens at run time, such as Cursor's session-start fallback outside $HOME (#1198), keeps its behavior tests (cursor-user-rules-hook.test.ts), and they move with the channel. When a resource moves to locate, its old resolver and the path tests that duplicate the matrix are deleted.

doctor

doctor keeps its wording. Each delivery check calls locate with the member's real env, platform and home, and adds a line when that says more:

  • ✖ when, under the member's env, the tool does not read the path (XDG_CONFIG_HOME set with OpenCode).
  • ⚠ when the read depends on the workspace or the session cwd ("Pi reads project skills only from the session cwd"). Where teamai already covers the gap at run time, as Cursor's session-start fallback does, doctor keeps checking that channel instead.
  • ✖ when a declared gate is not met: workspace trust, project trust, MCP approval, hooks.enabled, plugins.enabled.

A support page generated from the descriptor

docs/guide/tool-support.md and docs/guide/zh-CN/tool-support.md are generated from TOOLS with a snapshot test, as skill-data/core/references/commands.md is. The generator translates the legend and headers. Cell notes stay in English on both pages, and AGENTS.md records that exception. The README does not change.

Evidence, and the rule for reviewers

verified needs a recorded run: the tool version, the date and the command, in the PR. A check is committed under scripts/tool-checks/ only when it is cheap, offline and covers several cells of a tool that has had this kind of bug: Cursor's loaders, copilot instruction|skill|mcp list, codebuddy mcp list. CI never runs those scripts and never downloads a tool.

One bullet in AGENTS.md's Code Review Rules: a PR that adds a tool, changes where teamai writes for a tool, or changes what a tool supports updates its descriptor entry. A missing update is at most [P2 non-blocking]. A cell marked verified with no recorded run is [P3 nit]. Unknown is never a finding. What the matrix test checks stays out of review.

Windows

A windows-latest job runs on every PR, separate from E2E. The standard hosted runners are free for public repositories. It runs npm ci, npm run build, the matrix test, the hook rendering and path tests (a list of files, not the whole suite: 40 test files call bash today), and one real teamai hook-dispatch session-start for each hook shell (bash through Git Bash, PowerShell, node). It is not a required check at first, and becomes required once it stays green. A direct hook-dispatch run proves the shell, not which command field a tool reads. Codex's Windows hooks are tracked in #1209, #1210 and #1215, and the descriptor records that field with tool-side evidence.

Plan

flowchart LR
    fix1185["#1185<br/>(fix: #1204)"]:::main
    ca["#1218"]:::main
    dsh["#1219"]:::main
    la["#1220"]:::main
    A1["A1<br/>descriptor, skillLoad"] --> A2["A2<br/>Host + locate: skills"]
    fix1185 --> A2
    A2 --> B1["B1<br/>rules, blocks"]
    A2 --> B2["B2<br/>agents"]
    A2 --> B3["B3<br/>MCP"]
    A2 --> B4["B4<br/>hooks, self scope"]
    A2 --> E["E<br/>Windows job"]
    ca --> B2
    dsh --> B4
    la --> B4
    A1 --> C["C<br/>runtime and AI rows"]
    B1 & B2 & B3 & B4 & C --> D["D<br/>support page, review rule"]
    classDef main fill:#2f5f8f,stroke:#1f4569,color:#fff;
Loading

Blue issues are fixed on main first. B1 to B4 depend only on A2, so they can land in any order. The list gives each PR's check.

in parallel, on main
  #1204 and the three bugs in the next section

A1  descriptor skeleton: ToolId, home, skillLoad, tool list, home and channel tests; skill usage on the wildcard pass
      → verify: the skill-load test, fed payloads captured from real runs, fails for copilot, pi, omp, dsh, opencode 2.x and codex, and passes once tracking reads skillLoad; each skill counts once per turn
A2  Host and locate for skills: writers, doctor check, matrix test
      → verify: the matrix test fails on OpenCode's and Muse's XDG case, and passes after; Hermes and OpenClaw resolve through locate
B1  rules and instruction blocks
      → verify: the matrix covers Cursor's walk-up (#1197) and the subdirectory cases; the fallback's behavior tests still fail when the fallback is removed
B2  agents
B3  MCP, with the CodeBuddy and WorkBuddy lookup, ZCode, OpenCode's plugin and relocation branches, and the required format
      → verify: removing a tool's mcp format fails tsc
B4  hooks, with self scope, the main-checkout hooks, the local agent's hooks and the settings files `models switch` edits
      → verify: ZCode's self-scope case fails, then passes; doctor's fix text no longer loops
C   runtime and AI rows, data only
D   the generated support page in English and Chinese, and the AGENTS.md rule
E   the Windows job, any time after A2

Every B PR moves the resource's writers, its doctor check and its path tests to locate, and deletes the old resolver. Behavior tests of run-time channels stay.

The steps ship in three PRs, after the three bugs:

What this fixes, and what is fixed separately

Bugs that cause harm today are fixed on main without waiting for this proposal:

These are fixed by the proposal, in the PR named:

Done when

  • Every default tool has a descriptor entry. tsc fails for a tool without one, or without a stated hook channel, MCP format or agent format.
  • Skills, rules, instruction blocks, agents, MCP and hooks resolve through locate, and the old resolvers are gone.
  • The matrix test runs on every PR over darwin, linux and win32, the cwd inside $HOME, outside it and in a subdirectory, and each env override.
  • doctor reports the conditions and gates from locate.
  • docs/guide/tool-support.md and its Chinese page are generated from the descriptor.
  • The Windows job runs on every PR.
  • Adding a tool starts with its descriptor entry, and the compiler and the tests list what else it needs.

Matrix

Tool support by scope, with evidence (19 tools)

How the matrix was built

teamai fe3b9067, rechecked against 8dc60b08 for the changes since. Each run used an isolated HOME.

  • Real CLIs against a local mock model, with the request body captured: Claude Code 2.1.301 and 2.1.303, Codex 0.163.0, OpenCode 1.18.23 and 2.0.30, Pi 0.99.2, Oh My Pi 18.2.1. Self mode was run for Claude and Codex in a main checkout and a linked worktree.
  • Offline commands: Copilot CLI 1.0.98 (instruction list, skill list, mcp list), CodeBuddy 2.168.0 (mcp list), Muse 1.4.10 (skills list, exec --provider echo and its session log, with and without --trust-workspace), Pi (mcp list).
  • The tool's own loader, parser or schema, taken from its bundle or source and run on teamai's output: Cursor CLI 2026.09.21, .22 and .28 and Cursor IDE 3.16.29, CodeBuddy 2.168.0, Qoder CLI 1.1.73, Oh My Pi, Hermes (7e9eb8d9), OpenClaw (884a96d1), DeepSeek Harness (687f282e), ZCode v3.15.1's config schema.
  • Source only: ZCode v3.15.1, JoyCode 3.8.71, the WorkBuddy engine inside CodeBuddy 2.168.0.
  • Docs only: Kiro, Trae.

Not observed: any Windows run, any IDE session, and real Hermes, OpenClaw, dsh or ZCode sessions.

Status: ✓ works. ◐ works by default, and a condition in "Conditions that change a cell" breaks it while doctor still reports it delivered. ✗ broken in the default setup. + the tool supports it and teamai does not build it. — the tool has no such feature. ? unknown. ¹ the rules reach the tool but always on, as #946's ✓*.

Evidence: v a real tool run, or the tool's own loader, parser or schema run on teamai's output. s the tool's source or bundle. d docs (d3 third-party). t teamai's code only.

Defaults: macOS or Linux, the tool's default env, a session at the project root, the project inside $HOME.

User scope

Tool Skills Rules Instruction blocks Agents Hooks MCP Env Workflows Recall Sessions Stats
Claude Code ✓v ✓v ✓v ✓v ✓v ✓v ✓t ✓v ✓v ✓v ✓v
Codex ✓v ✓v¹ ✓v ✓v ◐v ✓v ✓t ✓t ✓v ✓v ✗v
Muse ◐v ◐v ◐v ◐v ◐v ◐v ✓t +v ✓t +s ✓v
Cursor IDE ✓s ✓v ✓v ✓s ✓s ✓s ? — ✓v ?s ✓t
Cursor CLI ✓v ✓v ✓v ✗v ✓s ✓s ? +s ✗v ?s ✓v
Copilot CLI ✓v ✓v ✓v ?s ✓s ✓v ? +s ✓v +s ✗v
OpenCode ◐v ◐v ◐v ◐v ◐s ◐v ? ✓t ✓v ✓t ✗v
Pi ◐v ✓v¹ ◐v —s ◐v ◐v ✓s +v ✓v +d ✗v
Oh My Pi ◐v ◐v ◐v ◐v ◐v ◐v ✓s +s ✓v +s ✗v
Hermes ◐v ◐v ◐v —s ◐v +s ◐s +s ✓v +s +s
OpenClaw ✓s ✓s ✓s +d ◐v +d ◐d +d ✓s +d +s
DeepSeek Harness ◐s ✓s¹ ✓s +s ✗s +s ✗s +s ✓s +s ✗v
ZCode ✓s ✓s¹ ✓s ✓s ✓s ✗s ✓t +s ✓s +s ✓s
CodeBuddy ◐s ◐v ◐s ◐s ◐s ◐v ? +t ✓s ✓s ✓v
WorkBuddy ✓d3 ✓t ✓t ✓t ✓t ◐s ? ? ✓t ✓t ?
JoyCode ✓s ✓s¹ ✓s ✗s —s +s ? ? ◐s ? ?
Kiro ✓d ◐d +d ✓d ◐d ✓d ? ? ◐d ? ?
Qoder, Qoder CN (CLI) ✓s ✓v +s ✓s ✓s ✓s ? ? ◐s ? ✓s
Trae, Trae CN ✓d3 ?t +d3 ?t ?t +t ? ? ? ? ?

Codex and OpenCode workflows hung or ran as the wrong agent at fe3b9067. #1192 fixed both (#1193), and they were not rerun.

Env reaches a tool only when the tool starts from a shell that sources teamai's profile block. A GUI, IDE or service launch gets nothing. Pi and Hermes have no subagents. teamai could render a team agent as a Pi prompt template (~/.pi/agent/prompts/<name>.md) or a Hermes skill, and a workflow step on Pi can pass the agent body with --append-system-prompt, which reaches the request.

Workspace scope

Hooks are user-level in workspace scope (resolveHookScope), so this column means "user hooks fire in this project".

Tool Skills Rules Instruction blocks Agents Hooks MCP Recall Stats
Claude Code ✓v ✓v ✓v ✓v ✓v ◐v ✓t ✓v
Codex ✓v ✓v¹ ✓v ◐v ◐v ◐v ◐t ✗v
Muse ◐v ✓v¹ +v ?v ✓v ◐v +v ✓v
Cursor (IDE and CLI) ✓v ✓v ✓v ✓v ✓s ◐s ✓v ✓t
Copilot CLI ✓v ✓v ✓v ?s ✓s ◐v ✓v ✗v
OpenCode ✓v ✓v¹ ✓v ✓v ✓s ✓v ✓v ✗v
Pi ◐v ✓v¹ ✓v —s ✓v ◐v ✓v ✗v
Oh My Pi ✓v ◐v ✓v ✓v ✓v ◐v ✓v ✗v
Hermes ◐v +v ◐v —s ◐s —s ◐v +s
OpenClaw ◐s —s —s +d —s +d —s +s
DeepSeek Harness ✓s ✗s +v +s ✗s +s +v ✗v
ZCode ✓s ◐v +v ◐s ✓s +s +v ✓s
CodeBuddy ◐s ◐v ◐s ◐s ◐s ◐v ◐s ✓v
WorkBuddy ?s ?t ?t ?s ?s ✗s ?t ?
JoyCode ✓s ✓v +s ✗s —s +s ◐s ?
Kiro ✓d ◐d +d ✓d ◐d ✓d ◐d ?
Qoder, Qoder CN (CLI) ✓s ✓v +s ✓s ✓s ◐s ◐s ✓s
Trae, Trae CN ✓d3 ✓t +d3 ?t ?t ◐d3 ? ?

WorkBuddy's cells are ? because teamai contradicts itself. The engine uses one projectDirName for project rules, skills, agents and settings. teamai writes project rules to .codebuddy/ and the rest to .workbuddy/. The WorkBuddy app's product.json settles it.

claude-internal, tclaude, codex-internal and tcodex are not installed. teamai writes their project files under <root>/.claude-internal/ and <root>/.codex-internal/. Claude Code keeps its project directory at .claude when CLAUDE_CONFIG_DIR moves (verified), and Codex's project layer is fixed at .codex (source). Their project files are probably unread unless the base tool is also enabled.

Self scope

In self mode, skills, rules, instruction blocks, agents and MCP use the same project paths as workspace scope, and a linked worktree gets every file through the post-checkout delivery (verified). Only hooks move, from $HOME into the project:

Tool Hooks in self scope
Claude Code ◐v. .claude/settings.local.json. Claude 2.1.303 skips every hook until workspace trust is accepted (source). -p runs them (verified)
Codex ◐v. The HOME dispatcher plus .codex/hooks.json. A linked worktree runs the main checkout's .codex/hooks.json and ignores its own (verified)
Muse ◐v. <root>/.muse/hooks.json runs only in a trusted workspace. No hook ran without --trust-workspace
Cursor ✓s. <git root>/.cursor/hooks.json
Copilot CLI ?s. .github/hooks/teamai.json. Whether repo hooks wait for folder trust is not traced
Qoder ◐s. <cwd>/.qoder/settings.json, skipped when the cwd is $HOME, shared with Qoder CN
CodeBuddy ◐s. Project settings are read from the session cwd only
WorkBuddy ?t
ZCode ✗v. teamai writes <root>/.zcode/cli/config.json, which ZCode never reads. doctor's fix (teamai hooks inject) writes nothing in self mode
Kiro ◐d. Inside teamai-rendered agents only, as in workspace scope
claude-internal, tclaude ?t. teamai writes <root>/.<build>/settings.json. Stock Claude keeps .claude as its project directory when its user config moves. These builds' own lookup is not verified
OpenCode, Oh My Pi, Pi, dsh, Hermes, OpenClaw as user scope: the HOME plugin, extension or hook

Self mode also appends team hooks into tracked hook files for Cursor, Qoder, CodeBuddy, WorkBuddy, Muse, ZCode and the internal builds, so the working tree is dirty after every pull (verified).

teamai's own AI

Tool AI backend (ai-client) Votes judge HTTP local agent hooks
Claude Code ✓v ✓v ✓v
Codex ✓v +v ✗v
Muse +v +v ✗t
Cursor +s +s — (refused)
Copilot CLI +s ?s —
OpenCode +v ?t ◐t
Pi +v ?t ?t
Oh My Pi +s ?t —
ZCode +s +s ✗v
CodeBuddy ?s +v ✓s
Qoder ?s ?s ✓s
Qoder CN ? ? ✗t
Hermes +s — ✗v
OpenClaw ✗s — ◐s
DeepSeek Harness +s ?s —

The AI backend is the CLI teamai runs for enrich, codebase, import, maintenance and the votes judge. ai-client tries claude, the internal builds, codex, codebuddy, workbuddy and openclaw. openclaw -p is not an OpenClaw command (its headless mode is openclaw agent --message). The votes judge parses Claude transcripts only. Codex, Muse and ZCode send last_assistant_message in their Stop payload, which the judge could read instead.

Conditions that change a cell

In each case doctor still reports the resource delivered, unless the row says otherwise.

Condition What stops being read Evidence
Session started in a project subdirectory Pi: skills, MCP. OMP: rules, MCP. CodeBuddy and WorkBuddy: rules, blocks, skills, agents, hooks, MCP. ZCode: agents v for Pi, OMP and CodeBuddy MCP. s for the rest
XDG_CONFIG_HOME set OpenCode and Muse: every user resource. Copilot 0.0.4xx: every user resource v, s for Copilot
Tool home moved by env or profile: PI_CODING_AGENT_DIR; OMP_PROFILE or --profile; CODEBUDDY_CONFIG_DIR; DSH_HOME (skills); OPENCLAW_HOME; OPENCLAW_PROFILE without --profile; a Hermes profile every user resource of that tool, and only skills for DSH_HOME v for Pi, OMP and CodeBuddy. s for the rest
CLAUDE_CONFIG_DIR outside $HOME every Claude user resource. doctor already reports it (toolRootRejection) v
Project or workspace not trusted, or project MCP not approved Codex: project agents. Muse: project skills, MCP, delegation, self-scope hooks. Pi: project skills and MCP. Claude: self-scope hooks in an interactive session. Copilot: project MCP. Claude, CodeBuddy, Qoder, Cursor: project MCP until approved v for Codex, Muse, Pi, Copilot, Claude -p, CodeBuddy. s for the rest
$HOME spelled differently from the path teamai trusted (a symlink, /tmp against /private/tmp) Codex: every teamai hook v
The bash login profile prints to stdout ZCode and dsh: the hook reply. doctor's ZCode probe accepts a substring v
Native Windows Codex: hooks, if it ignores commandWindows (#1215, reported on 0.142.5). Hermes: its home is %LOCALAPPDATA%\hermes. OpenClaw: spawn('teamai') cannot start teamai.cmd. Claude without Git Bash: hooks. CodeBuddy and WorkBuddy: session directory encoding. HOME different from %USERPROFILE%: Pi, OMP, ZCode, Codex and Claude use os.homedir() s, d
The team's teamai.yaml sets toolPaths for any tool every tool not listed loses its paths, and the listed tools freeze (#1185, fix in #1204) v
Other Hermes plugins fill the shared 8,000-character section budget Hermes workspace blocks v
~/.pi/agent/AGENTS.override.md exists Pi user blocks v
OpenClaw's gateway runs as a launchd or systemd service hooks when teamai is off the service PATH, and env s, d

Other findings

These are not covered above. Each becomes an issue when someone picks it up.

Activity

  1. self-assigned this
    on Oct 19, 2026
  2. SaulMoro commented on Oct 20, 2026

    @SaulMoro
    CollaboratorAuthor

    A1 is up as a draft: #1245 (after #1204).

    • TOOLS (src/tools.ts) holds each tool's home and how its agent loads a skill, each rule with its evidence: verified, source, docs or assumed.
    • Skill use is recorded on the wildcard post-tool-use pass for every tool: the skill tool, a read of an installed SKILL.md (shell reads included), /name, Codex $name, and the block Pi expands /skill:name into. Each skill counts once per turn.
    • The per-tool Skill hook and SKILL_TOOL_MATCHER are gone. OpenCode sends raw tool names.

    New evidence, reflected in the Matrix above:

    • Codex (real run): no skill tool. It loads with cat <path>/SKILL.md or a $name mention, both in one turn under the same turn_id.
    • Cursor CLI (real run): Read of SKILL.md.
    • OpenCode 2.x: skill with { id }.
    • Pi 0.99.2 expands /skill:name into the prompt before extensions see it.

    Open: Cursor may count once per conversation (generation_id equaled conversation_id in the one capture), and hosts with no turn id can lose a credit when a detached hook lands after the next prompt.

  3. SaulMoro commented on Oct 21, 2026

    @SaulMoro
    CollaboratorAuthor

    A1 merged in #1245. A2 and E are in #1252, on top of it.

    A2. locate for skills.

    • locate(tool, 'skills', install, host) answers where each tool takes skills, for every tool, scope and platform.
    • Every skills writer and reader asks it. That covers pull, the built-in stub, push, uninstall, the skill-use check, doctor, the dashboard and the local agent. skillsDirForTool is gone.
    • Homes moved by a variable are recorded once per machine in ~/.teamai/tool-homes.json: XDG_CONFIG_HOME for OpenCode and Muse, OPENCODE_CONFIG_DIR, HERMES_HOME, PI_CODING_AGENT_DIR, COPILOT_HOME, DSH_HOME, OpenClaw's state dir and profile, and Hermes's Windows default.
      • The record wins over a shell without the variable.
      • Doctor reports a shell that disagrees, and a record that is missing or unreadable.
    • When a tool's skills directory moves, pull releases teamai's unchanged copies from the old one. This is one pass over every tool, after the built-in stub. A copy that was edited, that another tool still uses, or that is reached through a link is kept.
    • Hermes, Copilot, dsh and OpenClaw hooks and rules follow the same recorded home. OpenCode's, Muse's and Pi's other files stay where they were until their B PR, and doctor notes it.

    E. Windows job.

    • A windows-latest job runs the matrix and an allow-list of Windows-safe tests. A guard test fails a listed file that gains a POSIX-only construct.
    • The job also runs one real hook-dispatch session-start per hook shell.
    • The hook-command tests now pin the platform they render for.
    • The job is not required yet.

    Verified. Every check below used the built CLI with an isolated HOME:

    • An XDG pull delivers OpenCode and Muse skills under $XDG.
    • A shell without the variable still writes there.
    • Moving the record back releases the unchanged copies and keeps the edited one.
    • The skill-use run still credits codex, claude, pi, codex, claude and claude.

    Not verified.

    Updated in the body above.

    • The locate signatures as shipped.
    • The recorded-home paragraph.
    • The home-move findings, split by whether a tool-side source exists.
    • Two older bugs found along the way: a pull from a removed cwd throws uv_cwd, and a file at Hermes's skills path stops the scope with ENOTDIR.
  4. 33 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesthelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions