core-modules · git:20261003.6ca1478 · 2026-10-03 · sha256 8739c997209832c3
core-modules git:20261003.6ca1478A
Immutable. This exact content never changes and is served at /api/v1/blob/8739c997209832c3.
---
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/tools/preview`** is the **`preview_server`** tool: a static file server over a project directory on a free localhost port, run on goroutines as a **`KindServer`** task of the same pool (no hard timeout unless the call names one). See **`docs/features/preview-server.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.Acquire` / `TrustGate.ProbeShared` (sessions, through the manager's `Pool`) or `TrustGate.Connect` / `TrustGate.Probe`** (they re-check immediately before the spawn); ACP client-supplied servers are the only ones that skip the gate, and they still go through the pool (`Pool.Acquire`). Sessions never own a server: `pool.go` shares one connection per resolved declaration - once per process for a global server, once per workspace for a project server or one naming `${CWD}` - hands out leases (`Client` is a handle on a shared `conn`), keeps the global ones up under `Keep` and stops the rest once unheld; a stdio server runs in its own process group (`client.go`). 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; `spawn_agent` `resume` continues a finished run of the session in its own child instead of a second child starting over) and **`internal/session/subagent.go`** owns child sessions (ordinary `sess_` ids, bundles nested under `<parent>/subagents/`, `ErrSubagentReadOnly`, `DeleteSessionTree`, and `SubagentSpec.Resume`, which reopens a finished child from its bundle for the next run). 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`; marketplaces added with `plugin marketplace add` in `marketplaces.go` (`AddMarketplace`, `InstallFromMarketplace` for `plugin install <plugin>@<marketplace>`, `UpdateMarketplace`; kept in `${CODDY_HOME}/skills/.marketplaces.json`, only installed plugins are updated, while a `skills.sources` entry keeps having every plugin installed); git clone via `internal/gitws.Clone`/`Pull`, or, for a marketplace entry `{"source":"archive"}`, a zip download in `archive.go` - https only, every address and redirect through the SSRF guard `remoteGuard`, a declared `sha256` checked, unpacked under size and entry caps with unsafe entries refused, skills taken from the plugin manifest (`skills` in `.claude-plugin/plugin.json`, else `skills/`, else a root `SKILL.md`, every `SKILL.md` only when there is no manifest), no git; materialized into `${CODDY_HOME}/skills` with a `.remote.json` lockfile that records each skill's installed `version`, the archive digest for an archive plugin without one). 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`. The folders a workspace reads are **`config.Skills.SearchDirs()`**: **`DefaultSkillDirs`** (**`${HOME}/.agents/skills`**, **`${CWD}/.agents/skills`**, **`${CODDY_HOME}/skills`**, **`${CWD}/.coddy/skills`**, always read, lowest priority first) and then the extra **`skills.dirs`**, which win a name over them; every reader of skills (the session manager, slash commands, **`GET /coddy/skills`**, delete, **`coddy skills list`**) goes through it, never through **`skills.dirs`** alone. A folder named twice is read once, at its last place; **`${CWD}`** and a relative entry expand against the session workspace (**`expandPath`**).
- **`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`: a chain of project folders - `.coddy/rules`, the shared `.agents/rules` as system `agents-dir`, `.cursor/rules`, `.claude/rules`, `.codex/rules` - of which only the first that holds a rule file is read, so another agent's mirror of the same rules is never loaded next to it; plus the operator's own `${CODDY_HOME}/rules` as system `user`, always read - `DefaultFactory(home)` adds it, and `Discover` accepts an absolute provider root for it; `Inspect` also names the folder read and the ones skipped for `coddy rules list`; 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 the project folder winning over `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`: `AlwaysOnRules` for the system prompt, `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 tool call activates rides in that call's result through `internal/agent/rules_activation.go`, and one a mention brings - `@name`, `@rule:name`, or one a mentioned path activates - in that user message through `internal/agent/mentions.go`; neither enters `{{.Rules}}`, and each comes once until a compaction folds it away) 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