feat: add agent navigation tools, context reminders, and edit safety checks - #2
Conversation
Add a lightweight, dependency-free post-edit syntax sanity check to the edit tool: JSON.parse for .json files, and a comment/string-aware brace/paren/bracket balance + unterminated-literal scan for common C-like/JS/TS languages. Advisory only - never blocks or rejects the edit. Surfaces as EditToolDetails.syntaxWarning, appended to the tool's text output, and shown in the TUI result render.
Adds a "recall" tool that searches the full session history (including entries evicted from the live context by compaction) for a case-insensitive substring match, so the model can recover exact error messages, code snippets, or earlier details that were summarized away. Registered as opt-in (like grep/find/ls), not part of default active tools.
…in vitest config The @cheetahbyte/* workspace aliases were missing from vitest.config.ts (only the legacy @earendil-works/@mariozechner scopes were aliased), so coding-agent tests failed to resolve @cheetahbyte/pi-ai, pi-agent-core, pi-tui, pi-client, and pi-protocol in a worktree without a prior `npm run build`. Mirrors the existing alias pattern already used here and in packages/client and packages/server.
Adds a "symbol" tool that searches for exact-name declarations (function, class, method, type, struct, etc.) across source files and returns the actual declaration with its extracted body, instead of raw grep-style text matches. Covers JS/TS, Python, Go, Rust, Java, and C# via a per-extension regex ruleset, with string/comment-aware brace matching for body extraction and an indentation-based fallback for Python. Respects .gitignore. Registered as read-only and opt-in (not in defaultActiveToolNames), matching grep/find/ls precedent.
Adds a new `outline` built-in tool that returns a compact, signature-only preview of a single file's top-level declarations (classes, functions, interfaces, types, structs, etc.), with methods nested one level under their class/impl/trait, using per-extension regex heuristics. Covers JS/TS, Python, Go, Rust, and Java/C#. Unsupported extensions get a plain "use read instead" message rather than an error. Output is capped at 500 declarations / 50KB with an actionable truncation notice, reusing the existing truncate.ts conventions. Registered like grep/find/ls: opt-in via --tools, included in createReadOnlyTool(Definitions)/createAllTool(Definitions), and exported from the package's public API. Not added to defaultActiveToolNames.
# Conflicts: # packages/coding-agent/CHANGELOG.md
# Conflicts: # packages/coding-agent/src/core/sdk.ts # packages/coding-agent/src/core/tools/index.ts # packages/coding-agent/src/index.ts
# Conflicts: # packages/coding-agent/src/core/sdk.ts # packages/coding-agent/src/core/tools/index.ts
Full-document review by a fresh-context subagent found 2 blockers, 15 minors, and 12 nits; a second subagent verified every fix. All applied: - autoCompact ran before_compaction only when no step was unfinished, making the hook unreachable on the live overflow path where the abandoned assistant step still occupies LaneState; now keyed on "not resuming a compaction step". - Hook-supplied overflow compactions wrote no step_attempt, so the once-per-input guard never counted them and hook-driven compact-and- retry was unbounded; for reason overflow the hook path now writes the compaction step_attempt first. - Empty compaction preparation is a real code branch, terminal for overflow. before_resume is invoked in the resume() dispatch. Pending deferred re-park verifies handle equality. resume() re-tags operation results as ResumeResult. MessageEntry declares terminate; options gain toolExecution; before_navigation loses its orphaned result fields; AbortRequestedRecord loses its unreachable reason field. - Reduction bullets restated (newest-attempt closure, newest-own entry); Tier B records lane moves and covers the overflow and cancellation traces; hook-measured usage is written as hook ledger records at all three append sites; assorted stale wording from before the deferred- closure, ledger, and B2 changes brought current.
Section 18 no longer references observability.md, which described the superseded ALS/global-context mechanism and is being removed. Absorbed the two load-bearing points: the multi-runtime rationale for rejecting ambient context (explicit arguments are the only portable abstraction), and the adapter framing - the application supplies ExecutionContext to bridge spans into OTel/Sentry/logs/metrics, pi ships no exporter and no vendor dependency, adapters allocate their own span/trace ids, and may use AsyncLocalStorage internally. Dropped the reading-list entry.
* feat: align sqlite storage with session lanes * fix: delete lane from entry, action * fix: errors and types * fix: tests * fix: usage records instead of session materialized * chore: 1 mig * fix * refactor(agent): move harness experimental changes to split branch * refactor and cleanup * fix: use assertJsonSerializable for session metadata * fix(sqlite): reject corrupt lane leafs on open * fix: rebuild cache * refactor: branch cache * feat: session stats * fix: enforce leases * feat(agent): enforce fenced SQLite writer leases * fix(agent): keep SQLite transactions synchronous * fix(agent): align SQLite storage with conformance suite --------- Co-authored-by: Christian Klotz <hello@christianklotz.co.uk>
This comment has been minimized.
This comment has been minimized.
| if (c === "'") { | ||
| state = "single"; | ||
| continue; | ||
| } | ||
| if (c === '"') { | ||
| state = "double"; | ||
| continue; | ||
| } | ||
| if (c === "`") { | ||
| state = "template"; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
checkBraceBalance treats every ' as a single-quote opener, so Rust lifetimes (fn foo<'a>(x: &'a str) -> &'a str {) leave the scanner in "single" state at EOF and swallow the function-body {, emitting false unterminated single-quoted string at end of file warnings on every edit; C# interpolated strings ($"{a}" + "}") similarly produce false unbalanced '}' errors, telling the model a well-formed edit is broken. Skip ' followed by an identifier or static for Rust, and treat $" as an interpolated string that tracks {} braces for C#-like languages.
Prompt for LLM
File packages/coding-agent/src/core/tools/syntax-check.ts:
Line 194 to 205:
`checkBraceBalance` treats every `'` as a single-quote opener, so Rust lifetimes (`fn foo<'a>(x: &'a str) -> &'a str {`) leave the scanner in `"single"` state at EOF and swallow the function-body `{`, emitting false `unterminated single-quoted string at end of file` warnings on every edit; C# interpolated strings (`$"{a}" + "}"`) similarly produce false `unbalanced '}'` errors, telling the model a well-formed edit is broken. Skip `'` followed by an identifier or `static` for Rust, and treat `$"` as an interpolated string that tracks `{}` braces for C#-like languages.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (c === "'") { | ||
| state = "single"; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
checkBraceBalance lacks language awareness and treats every ' as a single-quote opener, so Rust lifetimes (fn foo<'a>(x: &'a str)) force the scanner into "single" and swallow the following >, (, :, & until the next ', producing false unbalanced ')' with no matching '(' warnings on every edit to lifetime-bearing .rs files. Enter single-quote state only for plausible char literals—e.g., ' immediately followed by content and a closing ' within a few chars—and skip ' when immediately followed by an identifier character in Rust.
if (c === "'" && (isJsLike || isCharLiteralStart(content, i))) {
state = "single";
continue;
}Prompt for LLM
File packages/coding-agent/src/core/tools/syntax-check.ts:
Line 194 to 197:
`checkBraceBalance` lacks language awareness and treats every `'` as a single-quote opener, so Rust lifetimes (`fn foo<'a>(x: &'a str)`) force the scanner into `"single"` and swallow the following `>`, `(`, `:`, `&` until the next `'`, producing false `unbalanced ')' with no matching '('` warnings on every edit to lifetime-bearing `.rs` files. Enter single-quote state only for plausible char literals—e.g., `'` immediately followed by content and a closing `'` within a few chars—and skip `'` when immediately followed by an identifier character in Rust.
Suggested Code:
if (c === "'" && (isJsLike || isCharLiteralStart(content, i))) {
state = "single";
continue;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
…ks#7626) * fix(agent): own SQLite backend tests in storage package * fix(agent): use lexical disposal for SQLite tests
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Kody Review CompleteGreat news! 🎉 Keep up the excellent work! 🚀 Kody Guide: Usage and ConfigurationInteracting with Kody
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
feat: add agent navigation tools, context reminders, and edit safety checks
Pull Request Summary
This PR delivers a batch of agent capability improvements and safety guardrails for the coding agent, centered around three themes: better code navigation/search tools, smarter context management after compaction, and safer file edits.
New agent tools
symboltool — Lets the agent find a symbol's declaration by exact name (functions, classes, methods, types, structs, etc.) across source files, returning the actual declaration body rather than just matching text lines. It respects.gitignore, supports JS/TS, Python, Go, Rust, Java, and C#, and includes safety caps on matches, body sizes, and files scanned.outlinetool — Provides a compact structural overview of a single file (top-level declarations as signatures only, without bodies). This is a cheaper alternative to reading a whole file to understand its shape, supporting the same set of languages, and is intended to be used beforeread/grepto narrow in on relevant parts of large files.recalltool — Searches the full session history (including entries removed from the live context by compaction) for case-insensitive substring matches. This helps the agent recover exact error messages, code snippets, or earlier details that were summarized away during compaction.All three tools are registered across tool factories, read-only tool lists, the SDK exports, and the TUI.
Context management after compaction
A constraints reminder mechanism was added: every 15 turns after a compaction, an ephemeral reminder message containing the latest compaction summary's "Constraints & Preferences" bullets is appended near the end of the outgoing context. This counters the "lost in the middle" problem where the summary drifts away from reliable retrieval zones during long sessions. The reminder is built fresh at context-assembly time and is never persisted, so it cannot accumulate clutter or go stale. It stays silent when there's no prior compaction or no meaningful constraints section.
Edit tool safety guardrail
The
edittool now performs a lightweight, dependency-free post-edit syntax check: JSON files are validated withJSON.parse, and common C-like/JS/TS languages are scanned for unbalanced braces/parens/brackets and unterminated literals (comment/string-aware, conservative to avoid false positives). If an issue is detected, the tool surfaces an advisory warning in its result and the TUI display, without ever failing or reverting the edit.Developer experience
@cheetahbyte/*workspace packages directly to source, sonpm testno longer requires a full build.docs-referencesadded to.gitignore.