core-modules · git:20260919.48680a2 · 2026-09-19 · sha256 d301638c934a0be4

core-modules git:20260919.48680a2A

Immutable. This exact content never changes and is served at /api/v1/blob/d301638c934a0be4.

---
description: Main internal packages at a glance
globs: internal/**/*.go
alwaysApply: false
---

# Core modules (sketch)

- **`internal/acp`** - ACP RPC server, session lifecycle from editors.
- **`internal/agent`** - tool loop and LLM turns.
- **`internal/session`** - session manager and mode (`agent` / `plan` / `ask`).
- **`internal/config`** - YAML and flags, plus the **`FileWatcher`** (`watch.go`) that installs a `config.yaml` somebody else changed into a running process: a stat gate, `LoadWithPaths`, the process overrides re-applied through `Adjust`, and a marshal-comparison against the live document so a save this process made itself, a comment or a reordered key announces nothing. `coddy serve` points it at `session.Manager.ReplaceConfig`, which is the one choke point every surface already observes.
- **`internal/tools`** - filesystem, shell, todo, MCP merge, etc. Shell also owns the background task family (**`run_command`** **`background: true`** plus **`background_list`** / **`background_output`** / **`background_wait`** / **`background_stop`**), backed by **`internal/bgtask`**. See **`docs/features/background-tasks.md`**.
- **`internal/mcp`** - MCP transports (`client.go`, `remote.go`, `connect.go`), the merged server list with scope/origin labels (`manage.go`), and the **workspace trust gate** for project-local `.coddy/mcp.json` (`trust.go`, `gate.go`). That file arrives with the checkout, so its declarations are not started or contacted until the operator approves that exact declaration for that workspace: policy `mcp.project_trust` (`ask` default / `allow` / `deny`), approvals in `<home>/mcp-trust.json` keyed by canonical workspace + declaration digest. **Every connect or probe of a *configured* server must go through `TrustGate.Connect` / `TrustGate.Probe`** (they re-check immediately before the spawn); `mcp.Connect` directly is only for ACP client-supplied servers. Approval surfaces: `coddy mcp list|trust|untrust`, `POST /coddy/mcp/{name}/trust|untrust`, Settings → MCP servers. See `docs/features/mcp.md`.
- **`internal/bgtask`** - process-wide, session-scoped pool for work that outlives a tool call. A task is whatever a **`Runner`** starts (**`CommandRunner`** for shell commands) or whatever a **`LaunchFunc`** hands to **`Pool.Launch`**: that is how a subagent run becomes a **`KindAgent`** task, with **`Spec.Agent`** / **`Snapshot.Agent`** `{name, session_id}` naming the child session. One scheduling path (`start`) serves `Start`, `Adopt` and `Launch`; do not add a second scheduling mechanism or status surface.
- **`internal/subagents`** - subagent **definitions** (markdown + YAML frontmatter, `definition.go`: aliases, bounds, SHA-256 digest, `EffectiveTools` / `NarrowPermissionMode` / `ResolveTimeoutSeconds`), the **loader** (`loader.go`: `subagents.dirs` precedence, canonical **scopes** `builtin` / `user` / `project`, `deny` never reads project dirs), the **trust receipts** for project-scope files (`trust.go`: `TrustStore` at `<home>/subagents-trust.json`, `Decide`, keyed by canonical workspace + name + digest under `subagents.project_trust`), the process-wide **`Limiter`** (refuses, never queues), and the **catalog** (`catalog.go`: `BuildCatalog`, `PromptBlock`, `WriteListing`). Pure decisions only: no sessions, no loop. **`internal/agent/subagent.go`** applies them (spawn hook, permission relay, report; once the spawning turn has ended the relay hands a detached child's prompt to a **`DetachedPermissionBroker`** - the console's modal, or in `coddy serve` **`serve.Runtime`**, which offers it to every surface that is up (the HTTP server: the parent chat and a `--remote` console; a Telegram bot: the session's chat) and takes the first answer - and refuses with a reason where no broker exists) and **`internal/session/subagent.go`** owns child sessions (ordinary `sess_` ids, bundles nested under `<parent>/subagents/`, `ErrSubagentReadOnly`, `DeleteSessionTree`). Approval surfaces: `coddy agents list|trust|untrust` and `POST /coddy/subagents/{name}/trust|untrust`; **Settings → Subagents** in the web UI lists the catalog read-only. See `docs/features/subagents.md`.
- **`internal/hooks`** - operator **lifecycle hooks**: commands that read one JSON document on stdin at a point of a session and answer with an exit code plus optional JSON (`definition.go`: Claude Code's file shape, handler fields, `Parse`; `matcher.go`: exact-list-or-regex matchers with tool-name aliases such as `Bash` -> `run_command`; `loader.go`: `hooks.files`, canonical **scopes** `user` / `project`, `deny` never reads project files, `Source.Runnable`; `trust.go`: `TrustStore` at `<home>/hooks-trust.json`, receipts keyed by canonical workspace + workspace-relative file + digest under `hooks.project_trust`, `Loader.WithStore`; `catalog.go`: `BuildCatalog`, `FindSource`, `WriteListing`; `runner.go`: payload, sequential execution, timeouts and process groups via `internal/platform`, the exit-code contract, `Outcome` merging with the most restrictive decision winning). Pure decisions only: no sessions, no loop. **`internal/agent/hooks.go`** applies them: `PreToolUse` before the permission gate and `PostToolUse` / `PostToolUseFailure` after the tool in `executeToolCall`, `UserPromptSubmit` (reject, or context into the `## Hook context` prompt block) and `Stop` (follow-up submitted as a `[Stop hook]` user message, capped by `hooks.stop_loop_limit`) in the loop, `PreCompact` (veto) / `PostCompact` in `CompactSession`, `SubagentStart` (refuse, or context into the child's task) / `SubagentStop` in `subagent.go`, `Notification` (`permission_prompt`) before the permission prompt; **`internal/session/hooks.go`** fires `SessionStart` from the manager and persists its context as `hookContext`. Project-scope files follow `hooks.project_trust` like MCP declarations and subagent definitions; a held file is reported once per session as a `notice`-level UI log row. Approval surfaces: `coddy hooks list|trust|untrust`, `GET /coddy/hooks`, `POST /coddy/hooks/trust|untrust`. See `docs/features/hooks.md`.
- **`internal/skills`** - skill loading, enable/disable (`loader.go`, `disabled.go`), and remote install from repos / agents-standard marketplaces (`remote.go`, `manifest.go`, `plugin.go`: `Sync`/`SyncSource` (all or one source), `AddSource`, `RemoveRemote`/`DeleteSkill` (delete any on-disk skill; bundled = read-only via `SkillReadonly`), `ListSources`, `RemoveSource`, `CheckUpdates`, `UpdateSkill`; git clone via `internal/gitws.Clone`/`Pull`, materialized into `${CODDY_HOME}/skills` with a `.remote.json` lockfile that records each skill's installed `version`). Marketplace plugin `version` (and `SKILL.md` frontmatter `version`) are surfaced by `InstalledVersion` and drive update detection (`compareVersions`). Default dirs: `~/.agents/skills` (global, shared with `npx skills`/`npx skillsbd`), `~/.coddy/skills` (coddy-specific), `${CWD}/.coddy/skills` (project-local). Remote sources are listed in `skills.sources` and fetched only on demand. Management parity across the CLI (`coddy skills add|sync|remove`, `coddy plugin marketplace list|add|remove|sync`, `coddy plugin install|remove|enable|disable`), the built-in `/plugin` chat command (`internal/agent/plugin_command.go`, deterministic like `/compact`; shared dispatcher `skills.RunPluginCommand` / `MarketplaceStatus` in `plugin.go`), HTTP (`/coddy/skills/*`), and the Settings → Skills UI. See `docs/features/skills.md`.
- **`internal/mention`** - the one grammar of `@` mentions (`grammar.go`: relative, absolute, `~` and `../` paths, quoted paths, `:N-M` / `#L` ranges, folders, `@session:` / `@rule:` / `@agent:` / `@coddy:`, web pages; every reading of an ambiguous token, the one that exists on disk winning), path resolution (`paths.go`), the workspace index for completion (`index.go`: `git ls-files` or a walk, cached per root, rebuilt when a mention starts), fuzzy ranking (`fuzzy.go`) and the `<coddy_attachment>` element with its display twin `ForDisplay` (`attachment.go`). Pure, no sessions. `internal/session/mentions.go` resolves a prompt's mentions once, into that user message (a mention never fails the prompt and never moves the system prompt); `mention_search.go` answers completion for every surface (`GET /coddy/mentions`); `mention_check.go` runs the resolver dry for the web composer's highlight (`POST /coddy/mentions/check`). The SPA twin `external/ui/src/ui/skills/draftAt.ts` and the Go grammar are held to the same cases, `internal/mention/testdata/grammar_cases.json` - change both or neither. See `docs/features/mentions.md`.
- **`internal/docs`** - the documentation built into the binary: **`docs/embed.go`** carries `nav.yaml` and its pages, `internal/docs` splits them at headings (GitHub anchors shared with `internal/docsgen`), rewrites their links (`coddy:<slug>#<anchor>` between pages, GitHub at the release tag for images and repository files), ranks sections with BM25F and reads a page or a section under a byte budget. Pure: it reads only the embedded tree. Every surface goes through it - `coddy_docs_search` / `coddy_docs_read`, the `@coddy:` mention, `GET /coddy/docs*`, the web reader, the console's F1 help, `coddy docs`. A new group folder under `docs/` must be added to the embed pattern. See `docs/features/built-in-docs.md`.
- **`internal/rules`** - rules catalog: one provider per root (`factory.go`: `.coddy/rules`, the shared `.agents/rules` as system `agents-dir`, `.cursor/rules`, `.claude/rules`, `.codex/rules`, plus the operator's own `${CODDY_HOME}/rules` as system `user` - `DefaultFactory(home)` adds it, and `Discover` accepts an absolute provider root for it; nested `AGENTS.md` files have no provider, `agents.go` reads them on demand through `AgentsForPaths` for the folders a tool enters and never walks the tree), dedupe by file name with precedence coddy > agents-dir > cursor > claude > codex > agents > user, the dialect picked by extension in `markdown.go` (`.mdc` = Cursor `description`/`globs`/`alwaysApply`, default manual; `.md` = Claude Code `paths`, unconditional without them; a header is read as YAML first, then by a lenient line reader because Cursor's own `globs: **/*.go` is not valid YAML), doublestar glob matching anchored at `Rule.Root` (`MatchGlob`), activation (`select.go`: `MatchAuto` for the paths a prompt mentions - `@path` or an editor's `file://` attachment; `scope.go`: `MatchScoped` for filesystem tool-call paths against globs and nested `AGENTS.md` subtrees; a rule a mention brings - `@name`, `@rule:name`, or one a mentioned path activates - rides in that user message through `internal/agent/mentions.go`, never in `{{.Rules}}`) and the `coddy rules list` table (`list.go`: `RenderCatalog`). `LoadProjectDocs(home, cwd)` reads the agent home's `AGENTS.md` and `DESIGN.md` first, above the workspace's own pair, with nothing in `config.yaml` naming them, and `AgentsForPaths` reads the same pair (`preambleFiles`) for a folder a tool enters; `RenderPrompt` reports the paths it embedded, so `internal/session/instructions_load.go` (the `instructions.files` reader: `${CODDY_HOME}`, `${CWD}`, `~` and absolute entries) never sends the same file twice. See `docs/features/rules.md`.

Prefer extending these over growing **`cmd/`** or duplicating logic in **`external/`**.

## References

@architecture.md