* fix(core): resolve dangling symlinks in the filesystem sandbox check
The sandbox resolver treated a dangling symlink as a path that does not exist yet, so the containment check passed on the link's own location and file_write then created the link's target, which could be outside the agent's working directory. The resolver now follows a dangling symlink to the target it names, rejects targets outside the root, keeps in-root targets writable, and bounds resolution at 40 hops so a loop fails the check.
Addresses GHSA-h42w-g3ch-rjjm (CVE-2026-108600).
* fix(core): fail the sandbox check when realpath reports ELOOP
A symlink chain longer than the kernel's link limit made realpath fail with ELOOP, and the resolver then followed the first link by hand and resolved the rest, accepting a path the kernel itself refuses. ELOOP from realpath now fails the check, and the resolver's own hop limit stays as the bound for links it follows by hand.
* fix(core): resolve symlink targets one component at a time
Following a dangling symlink, the resolver normalized the target with path.resolve, which collapses a/.. lexically. When a is itself a symlink the kernel steps out of the directory a points to, so a target such as pivot/../created.txt resolved to a different file than a write through the link would create. The resolver now walks the target component by component, following symlinks before applying .., and fails when .. would step out of a missing directory, as the kernel does.
The previous test for a self-referencing target built the link with path.join, which folded ghost/../self to self, so it only exercised a plain loop. Link targets in these tests are now joined without normalization.
* fix(core): keep the directory requirement of a trailing separator in symlink targets
A symlink target ending in a separator or "." names a directory, so the kernel refuses to open or create a file through it. The resolver dropped those components, and native realpath does the same on macOS, so a link to victim/ resolved to the file victim and file_write overwrote it. The resolver now requires an existing directory at the end of such a target, and confirms each realpath result with stat, which takes the same lookup as open, before trusting it.
Systematic documentation review: 12 new docs pages (coordinator, streaming, budgets and limits, errors, hooks and callbacks, glossary, production checklist, Run Viewer, MCP, sandbox and shell, evaluation in CI, routing evaluation) plus a docs index; the three longest guides split with stub sections that keep existing anchors; observability release records moved to docs/internal; statements re-verified against the code and corrected; READMEs, AGENTS.md, CONTRIBUTING.md, and issue templates synced. Documentation only, no behavior change.