Skip to content

Feat/context budget controls#10

Merged
d-teller merged 21 commits into
mainfrom
feat/context-budget-controls
Jul 8, 2026
Merged

Feat/context budget controls#10
d-teller merged 21 commits into
mainfrom
feat/context-budget-controls

Conversation

@d-teller

@d-teller d-teller commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

No description provided.

TestMemorySink_Put previously only checked len(stored) == 1, never
verifying the callback received the correct value. Now asserts the
stored value matches and the returned URI ends with the stored key.

Also apply filepath.Base to agentName in tempFileSink.Put for
consistency with the existing key sanitization, preventing a
malicious agentName from escaping ~/.abbyfile/.
Extend CompiledDefaults with MaxOutputLines, MaxOutputBytes, OnOverflow,
and EagerInstructions so `config get`/`config get context_budget.*`
displays effective context_budget values and their source
(compiled vs override), matching the existing model/tool_timeout rows.
compiledDefaults() now populates these from the agent's tools.ContextBudget.

Add an integration test that builds a real agent binary with a custom
tool emitting 1000 lines and a context_budget capped at 20 lines,
then runs it via `run-tool` and asserts the shaped output is elided
and far shorter than the raw output — locking in end-to-end shaping
behavior wired in earlier tasks.
Add docs/guides/context-budget.md covering output shaping (head-tail,
spill, passthrough), the context_budget frontmatter block, shipped
defaults, config-set/get overrides, the --subagent flag, and
eager_instructions. Update concepts.md and README.md to note that
--subagent gives Abbyfile agents real context isolation.
…mited

Final whole-branch review found two correctness bugs at the codegen/runtime
boundary: buildBudgetData was forcing an empty per-tool on_overflow to
head-tail, defeating effectiveFor's inherit-from-base semantics; and
MaxOutputLines/MaxOutputBytes were plain ints, so frontmatter couldn't
distinguish "0 (unlimited)" from "omitted (use shipped default)".
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add context budget controls: tool output shaping + sub-agent isolation

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds a ContextBudget shaper that caps tool output via head-tail, spill-to-URI, or passthrough.
• Wires budget from frontmatter + runtime config through codegen, agent runtime, and tool executor.
• Adds spill sinks (memory/temp-file), MCP handshake eager-instruction gating, and `abby build
 --subagent` emission.
• Ships a full guide/spec/plan plus unit and integration tests.
Diagram

graph TD
  A["Agent .md frontmatter"] --> B["definition.ContextBudgetDef"] --> C["builder codegen"] --> D["Generated main.go"] --> E["agent.Agent runtime"]
  F["config.yaml overrides"] --> E
  E --> G["tools.Executor"] --> H{"Over budget?"}
  H -->|"no"| I["Return raw output"]
  H -->|"head-tail"| J["Truncated preview"]
  H -->|"spill"| K[("SpillSink")] --> J
  E --> L["mcp.Bridge handshake"]
  M["abby build --subagent"] --> N[".claude/agents/<name>.md"]

  subgraph Legend
    direction LR
    _svc(["Component"]) ~~~ _db[("Storage")] ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The chosen layered design is appropriate: always-on output shaping provides a default safety net for every agent, while --subagent enables true context isolation for callers that need it. The spill-to-URI option preserves debuggability without breaking the budget, and the eager-instructions gating reduces handshake token cost while keeping full instructions available on demand.

Files changed (32) +4207 / -68

Enhancement (14) +801 / -54
build.goAdd '--subagent' flag and emit Claude Code sub-agent files +20/-2

Add '--subagent' flag and emit Claude Code sub-agent files

• Introduces '--subagent' flag (also implied by '--plugin') and generates '.claude/agents/<name>.md' files via the new subagent package during builds.

cmd/abby/build.go

config.goExpose context_budget fields via 'config get' output +75/-3

Expose context_budget fields via 'config get' output

• Extends compiled defaults and config printing to show compiled vs override values for selected context_budget fields (lines/bytes/overflow/eager_instructions).

internal/cli/config.go

serve_mcp.goPass eagerInstructions into MCP BridgeConfig +12/-10

Pass eagerInstructions into MCP BridgeConfig

• Updates serve-mcp command construction to accept eagerInstructions and forward it to the MCP bridge.

internal/cli/serve_mcp.go

agent.goDefault and apply ContextBudget in agent runtime; wire sink + MCP behavior +55/-13

Default and apply ContextBudget in agent runtime; wire sink + MCP behavior

• Adds a default ContextBudget, applies config overrides, uses a memory or temp-file spill sink depending on memory enablement, shapes tool outputs via executor, and threads eager-instructions to serve-mcp/MCP bridge.

pkg/agent/agent.go

options.goAdd agent.WithContextBudget option +5/-0

Add agent.WithContextBudget option

• Introduces an Option to set the compiled-in context budget for generated/embedded agents.

pkg/agent/options.go

builder.goGenerate codegen budget data with correct defaulting/inheritance +107/-0

Generate codegen budget data with correct defaulting/inheritance

• Adds budgetData and buildBudgetData to map definition context_budget into codegen template data, applying shipped defaults for omitted base fields while preserving per-tool inheritance semantics.

pkg/builder/builder.go

main.go.tmplEmit WithContextBudget into generated main.go +25/-1

Emit WithContextBudget into generated main.go

• Template renders agent.WithContextBudget(tools.ContextBudget{...}) including per-tool overrides when present, and conditionally imports tools when budget or custom tools exist.

pkg/builder/templates/main.go.tmpl

loader.goImplement dotted-key context_budget config writes and reset +63/-0

Implement dotted-key context_budget config writes and reset

• Adds 'context_budget.*' cases to WriteFieldTo with parsing/validation, plus ensureBudget helper and ResetFieldTo support for context_budget.

pkg/config/loader.go

agent.goParse + validate context_budget frontmatter without importing tools +81/-18

Parse + validate context_budget frontmatter without importing tools

• Introduces ContextBudgetDef (self-contained) and wires it into both dual and single frontmatter parsing; adds validation for legal strategies and non-negative numeric fields, including per_tool entries.

pkg/definition/agent.go

bridge.goGate handshake instructions behind eager_instructions flag +24/-3

Gate handshake instructions behind eager_instructions flag

• Adds EagerInstructions to BridgeConfig and a handshakeInstructions helper that returns a short stub unless eager injection is enabled.

pkg/mcp/bridge.go

subagent.goNew subagent generator for Claude Code '.claude/agents/<name>.md' +76/-0

New subagent generator for Claude Code '.claude/agents/<name>.md'

• Implements markdown emission with YAML frontmatter and a Return Protocol section that enforces a bounded summary; summary cap uses context_budget.summary_lines when set.

pkg/subagent/subagent.go

executor.goShape tool output and returned error messages in executor +26/-4

Shape tool output and returned error messages in executor

• Adds WithContextBudget option and applies shaping to builtin and CLI tool outputs; logs full stderr while returning shaped error message for callers.

pkg/tools/executor.go

shaper.goNew ContextBudget shaper with per-tool overrides and rune-safe byte truncation +173/-0

New ContextBudget shaper with per-tool overrides and rune-safe byte truncation

• Implements head-tail and spill strategies with a hard byte-cap backstop that avoids splitting UTF-8 runes; supports per-tool effective budget merge and spill degradation when sink is unavailable.

pkg/tools/shaper.go

spill.goAdd spill sinks for memory:// and file:// URIs +59/-0

Add spill sinks for memory:// and file:// URIs

• Implements memory-backed spill via injected setter and temp-file spill under ~/.abbyfile/<agent>/spill/, including agent name sanitization and content hashing for keys.

pkg/tools/spill.go

Tests (11) +1167 / -0
config_test.goAdd CLI tests for context_budget 'config get' +104/-0

Add CLI tests for context_budget 'config get'

• Adds tests verifying compiled defaults and overrides are correctly reported for context_budget fields.

internal/cli/config_test.go

budget_test.goIntegration test: real binary shapes oversized tool output +104/-0

Integration test: real binary shapes oversized tool output

• Builds a standalone agent with a capped context_budget and asserts 'run-tool' output is elided and bounded (integration build tag).

internal/integration/budget_test.go

agent_test.goTest applying context_budget config overrides +36/-0

Test applying context_budget config overrides

• Adds coverage ensuring partial overrides apply while leaving unspecified fields at shipped defaults.

pkg/agent/agent_test.go

builder_test.goGuard context budget codegen with string and AST-parse tests +166/-0

Guard context budget codegen with string and AST-parse tests

• Adds tests that verify WithContextBudget emission, ensure generated Go parses, and lock in nil-vs-0 (unlimited) and per-tool on_overflow inheritance behavior.

pkg/builder/builder_test.go

config_test.goTest config set/reset for context_budget dotted keys +44/-0

Test config set/reset for context_budget dotted keys

• Adds tests verifying context_budget subfield writes merge (don’t clobber) and that resetting the whole block clears overrides; validates strategy values.

pkg/config/config_test.go

agent_test.goAdd frontmatter parsing tests for context_budget +212/-0

Add frontmatter parsing tests for context_budget

• Adds tests for dual/single parsing, per_tool overrides, invalid-strategy/negative rejection, and explicit 0 meaning unlimited (distinct from omitted).

pkg/definition/agent_test.go

bridge_internal_test.goWhite-box tests for handshakeInstructions behavior +74/-0

White-box tests for handshakeInstructions behavior

• Ensures non-eager handshake does not leak full prompt content while eager handshake includes it.

pkg/mcp/bridge_internal_test.go

subagent_test.goTests for sub-agent markdown emission and YAML correctness +126/-0

Tests for sub-agent markdown emission and YAML correctness

• Validates path, frontmatter round-trip/escaping, tool list emission, return-protocol guidance, and summary_lines default/override behavior.

pkg/subagent/subagent_test.go

executor_shaping_test.goExecutor shaping tests for success and failure cases +64/-0

Executor shaping tests for success and failure cases

• Verifies executor shapes oversized builtin outputs and CLI error messages when configured, and leaves outputs unchanged without a budget.

pkg/tools/executor_shaping_test.go

shaper_test.goComprehensive unit tests for shaping semantics +162/-0

Comprehensive unit tests for shaping semantics

• Covers under-cap passthrough, head/tail elision marker, byte-cap backstop, passthrough strategy, per-tool merging, spill and degradation respecting byte caps, and UTF-8 validity after truncation.

pkg/tools/shaper_test.go

spill_test.goTest spill sinks including agentName path sanitization +75/-0

Test spill sinks including agentName path sanitization

• Ensures memory sink round-trips value/URI, temp-file sink persists correct content, and malicious agent names cannot escape ~/.abbyfile/.

pkg/tools/spill_test.go

Documentation (5) +2214 / -2
README.mdDocument context budget and subagent isolation entry points +3/-1

Document context budget and subagent isolation entry points

• Updates the comparison table to note Abbyfile context isolation via 'abby build --subagent' and adds links to the new Context Budget guide.

README.md

concepts.mdCross-link to Context Budget guide from sub-agent concepts +1/-1

Cross-link to Context Budget guide from sub-agent concepts

• Adds a note that Abbyfile agents can achieve context isolation using 'abby build --subagent', with a link to the guide.

docs/concepts.md

context-budget.mdAdd Context Budget Guide +199/-0

Add Context Budget Guide

• New guide documenting output shaping vs sub-agent isolation, the 'context_budget' frontmatter schema, defaults, overflow strategies, runtime overrides, and eager-instructions behavior.

docs/guides/context-budget.md

2026-07-07-context-budget-controls.mdAdd context-budget controls implementation plan +1662/-0

Add context-budget controls implementation plan

• Adds a detailed implementation plan document for the context budget controls work.

docs/superpowers/plans/2026-07-07-context-budget-controls.md

2026-07-07-context-budget-controls-design.mdAdd context-budget controls design spec +349/-0

Add context-budget controls design spec

• Adds a design spec covering goals, constraints, and architecture for context budget controls.

docs/superpowers/specs/2026-07-07-context-budget-controls-design.md

Other (2) +25 / -12
root.goThread EagerInstructions through CLI options +12/-11

Thread EagerInstructions through CLI options

• Adds EagerInstructions field to CLI root Options to pass through to MCP serving.

internal/cli/root.go

config.goAdd ContextBudgetOverride to runtime config schema +13/-1

Add ContextBudgetOverride to runtime config schema

• Extends Config with a context_budget override block, with pointer fields for each scalar so overrides can be partial and distinguish unset vs set.

pkg/config/config.go

@d-teller d-teller merged commit 13b7046 into main Jul 8, 2026
5 checks passed
@d-teller d-teller deleted the feat/context-budget-controls branch July 8, 2026 11:35
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Memory spill always fails 🐞 Bug ≡ Correctness
Description
When OverflowSpill is selected, ContextBudget.Shape generates a spill key containing '/', but the
memory-backed sink is wired to memory.Manager.Set which rejects keys with path separators. This
makes spill-to-memory fail every time (degrading to head-tail truncation) whenever memory is
enabled.
Code

pkg/tools/shaper.go[R121-123]

+			key := fmt.Sprintf("spill/%s", toolName)
+			if uri, err := sink.Put(key, raw); err == nil {
+				res.SpillURI = uri
Evidence
The shaper generates a spill key with a slash and passes it to SpillSink.Put; the agent chooses a
memory-backed sink using memory.Manager.Set; and the memory store explicitly rejects keys containing
path separators, so the spill write fails and the shaper degrades.

pkg/tools/shaper.go[118-133]
pkg/agent/agent.go[239-247]
pkg/tools/spill.go[21-27]
pkg/memory/store.go[69-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`tools.ContextBudget.Shape()` uses spill keys like `spill/<toolName>`. When memory is enabled, the spill sink is `tools.NewMemorySink(..., mgr.Set)`, and the underlying `memory.FileStore` rejects keys containing `/` or `\`. This causes all memory-backed spills to fail and the shaper to degrade to truncation.

### Issue Context
- Memory storage (`memory.FileStore`) enforces flat keys (no path separators).
- The spill feature is expected to store full output losslessly when `on_overflow: spill`.

### Fix Focus Areas
- pkg/tools/spill.go[21-27]
- pkg/tools/shaper.go[118-133]
- pkg/memory/store.go[69-80]
- pkg/tools/spill_test.go[10-41]

### Implementation notes
- Prefer fixing this in `memorySink.Put()` by sanitizing `key` into a memory-safe key (e.g., replace `/` and `\` with `_`), then append the hash.
- Optionally also change `ContextBudget.Shape()` to generate a memory-safe base key (e.g. `spill_<toolName>`) so *all* sinks receive safe keys.
- Add/adjust a test asserting the stored memory key contains no path separators.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Negative budget disables shaping 🐞 Bug ☼ Reliability
Description
Context budget overrides from config.yaml are applied without validating non-negative values, and
the shaper only enforces caps when MaxOutputLines/MaxOutputBytes are > 0. A negative override
therefore disables shaping entirely (or yields misleading shaping behavior) without any warning.
Code

pkg/agent/agent.go[R151-173]

+	if cfg.ContextBudget != nil {
+		cb := cfg.ContextBudget
+		if cb.MaxOutputLines != nil {
+			a.budget.MaxOutputLines = *cb.MaxOutputLines
+		}
+		if cb.MaxOutputBytes != nil {
+			a.budget.MaxOutputBytes = *cb.MaxOutputBytes
+		}
+		if cb.OnOverflow != nil {
+			a.budget.OnOverflow = tools.OverflowStrategy(*cb.OnOverflow)
+		}
+		if cb.HeadLines != nil {
+			a.budget.HeadLines = *cb.HeadLines
+		}
+		if cb.TailLines != nil {
+			a.budget.TailLines = *cb.TailLines
+		}
+		if cb.SummaryLines != nil {
+			a.budget.SummaryLines = *cb.SummaryLines
+		}
+		if cb.EagerInstructions != nil {
+			a.budget.EagerInstructions = *cb.EagerInstructions
+		}
Evidence
The config writer parses integers with strconv without any range checks, the agent applies those
values directly to the runtime budget, and shaping only triggers when caps are greater than zero—so
negative values bypass shaping.

pkg/config/loader.go[96-146]
pkg/agent/agent.go[151-173]
pkg/tools/shaper.go[95-102]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`pkg/config/loader.WriteFieldTo()` accepts negative integers for context_budget numeric fields, and `Agent.applyConfigOverrides()` blindly copies them into the runtime budget. Since `ContextBudget.Shape()` only triggers caps when values are `> 0`, negative values effectively turn off shaping without any log warning.

### Issue Context
Frontmatter context_budget values are validated, but config.yaml overrides (manual edits or `config set`) currently are not.

### Fix Focus Areas
- pkg/config/loader.go[96-146]
- pkg/agent/agent.go[151-173]
- pkg/tools/shaper.go[95-102]

### Implementation notes
- In `WriteFieldTo`, reject negative values for `max_output_lines`, `max_output_bytes`, `head_lines`, `tail_lines`, and `summary_lines` with a clear error.
- In `applyConfigOverrides`, defensively validate and ignore invalid negative values (log a warning and keep the previous/default value), so manual config edits can’t silently disable shaping.
- Consider validating `on_overflow` here too (ignore invalid strategy and warn), to match frontmatter validation behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Config CLI hides budget fields 🐞 Bug ◔ Observability
Description
The CLI config get command does not support or print several context_budget override fields
(head_lines, tail_lines, summary_lines) even though they exist in the config schema and can be
written. This makes it difficult to verify/debug runtime override behavior for those fields.
Code

internal/cli/config.go[R130-163]

+	case "context_budget.max_output_lines":
+		val := defaults.MaxOutputLines
+		source := "compiled"
+		if cfg.ContextBudget != nil && cfg.ContextBudget.MaxOutputLines != nil {
+			val = *cfg.ContextBudget.MaxOutputLines
+			source = "override"
+		}
+		fmt.Fprintf(cmd.OutOrStdout(), "%d (%s)\n", val, source)
+	case "context_budget.max_output_bytes":
+		val := defaults.MaxOutputBytes
+		source := "compiled"
+		if cfg.ContextBudget != nil && cfg.ContextBudget.MaxOutputBytes != nil {
+			val = *cfg.ContextBudget.MaxOutputBytes
+			source = "override"
+		}
+		fmt.Fprintf(cmd.OutOrStdout(), "%d (%s)\n", val, source)
+	case "context_budget.on_overflow":
+		val := defaults.OnOverflow
+		source := "compiled"
+		if cfg.ContextBudget != nil && cfg.ContextBudget.OnOverflow != nil {
+			val = *cfg.ContextBudget.OnOverflow
+			source = "override"
+		}
+		fmt.Fprintf(cmd.OutOrStdout(), "%s (%s)\n", val, source)
+	case "context_budget.eager_instructions":
+		val := defaults.EagerInstructions
+		source := "compiled"
+		if cfg.ContextBudget != nil && cfg.ContextBudget.EagerInstructions != nil {
+			val = *cfg.ContextBudget.EagerInstructions
+			source = "override"
+		}
+		fmt.Fprintf(cmd.OutOrStdout(), "%t (%s)\n", val, source)
	default:
-		return fmt.Errorf("unknown field: %s (supported: model, tool_timeout)", field)
+		return fmt.Errorf("unknown field: %s (supported: model, tool_timeout, context_budget.max_output_lines, context_budget.max_output_bytes, context_budget.on_overflow, context_budget.eager_instructions)", field)
Evidence
The config schema and writer support head/tail/summary fields, but the CLI config inspection only
implements max_output_* and eager_instructions/on_overflow, so those supported overrides are not
visible through the CLI.

pkg/config/config.go[31-40]
pkg/config/loader.go[119-139]
internal/cli/config.go[108-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`internal/cli/config.go` only supports reading/printing a subset of `context_budget.*` fields, but `pkg/config` and `pkg/config/loader` support additional fields (`head_lines`, `tail_lines`, `summary_lines`). Users can set these fields but cannot inspect effective values via `config get`.

### Issue Context
This is a CLI observability/debuggability gap: it doesn’t change runtime behavior, but it makes misconfiguration hard to detect.

### Fix Focus Areas
- internal/cli/config.go[108-231]
- internal/cli/config_test.go[39-142]
- internal/cli/config.go[11-20]
- pkg/agent/agent.go[93-103]

### Implementation notes
- Extend `cli.CompiledDefaults` to include `HeadLines`, `TailLines`, and `SummaryLines` (and optionally document them).
- Populate them in `Agent.compiledDefaults()`.
- Add `printField` + `printAllFields` cases for:
 - `context_budget.head_lines`
 - `context_budget.tail_lines`
 - `context_budget.summary_lines`
- Update the supported-field error string and add/extend tests to cover these fields.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread pkg/tools/shaper.go
Comment on lines +121 to +123
key := fmt.Sprintf("spill/%s", toolName)
if uri, err := sink.Put(key, raw); err == nil {
res.SpillURI = uri

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Memory spill always fails 🐞 Bug ≡ Correctness

When OverflowSpill is selected, ContextBudget.Shape generates a spill key containing '/', but the
memory-backed sink is wired to memory.Manager.Set which rejects keys with path separators. This
makes spill-to-memory fail every time (degrading to head-tail truncation) whenever memory is
enabled.
Agent Prompt
### Issue description
`tools.ContextBudget.Shape()` uses spill keys like `spill/<toolName>`. When memory is enabled, the spill sink is `tools.NewMemorySink(..., mgr.Set)`, and the underlying `memory.FileStore` rejects keys containing `/` or `\`. This causes all memory-backed spills to fail and the shaper to degrade to truncation.

### Issue Context
- Memory storage (`memory.FileStore`) enforces flat keys (no path separators).
- The spill feature is expected to store full output losslessly when `on_overflow: spill`.

### Fix Focus Areas
- pkg/tools/spill.go[21-27]
- pkg/tools/shaper.go[118-133]
- pkg/memory/store.go[69-80]
- pkg/tools/spill_test.go[10-41]

### Implementation notes
- Prefer fixing this in `memorySink.Put()` by sanitizing `key` into a memory-safe key (e.g., replace `/` and `\` with `_`), then append the hash.
- Optionally also change `ContextBudget.Shape()` to generate a memory-safe base key (e.g. `spill_<toolName>`) so *all* sinks receive safe keys.
- Add/adjust a test asserting the stored memory key contains no path separators.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread pkg/agent/agent.go
Comment on lines +151 to +173
if cfg.ContextBudget != nil {
cb := cfg.ContextBudget
if cb.MaxOutputLines != nil {
a.budget.MaxOutputLines = *cb.MaxOutputLines
}
if cb.MaxOutputBytes != nil {
a.budget.MaxOutputBytes = *cb.MaxOutputBytes
}
if cb.OnOverflow != nil {
a.budget.OnOverflow = tools.OverflowStrategy(*cb.OnOverflow)
}
if cb.HeadLines != nil {
a.budget.HeadLines = *cb.HeadLines
}
if cb.TailLines != nil {
a.budget.TailLines = *cb.TailLines
}
if cb.SummaryLines != nil {
a.budget.SummaryLines = *cb.SummaryLines
}
if cb.EagerInstructions != nil {
a.budget.EagerInstructions = *cb.EagerInstructions
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Negative budget disables shaping 🐞 Bug ☼ Reliability

Context budget overrides from config.yaml are applied without validating non-negative values, and
the shaper only enforces caps when MaxOutputLines/MaxOutputBytes are > 0. A negative override
therefore disables shaping entirely (or yields misleading shaping behavior) without any warning.
Agent Prompt
### Issue description
`pkg/config/loader.WriteFieldTo()` accepts negative integers for context_budget numeric fields, and `Agent.applyConfigOverrides()` blindly copies them into the runtime budget. Since `ContextBudget.Shape()` only triggers caps when values are `> 0`, negative values effectively turn off shaping without any log warning.

### Issue Context
Frontmatter context_budget values are validated, but config.yaml overrides (manual edits or `config set`) currently are not.

### Fix Focus Areas
- pkg/config/loader.go[96-146]
- pkg/agent/agent.go[151-173]
- pkg/tools/shaper.go[95-102]

### Implementation notes
- In `WriteFieldTo`, reject negative values for `max_output_lines`, `max_output_bytes`, `head_lines`, `tail_lines`, and `summary_lines` with a clear error.
- In `applyConfigOverrides`, defensively validate and ignore invalid negative values (log a warning and keep the previous/default value), so manual config edits can’t silently disable shaping.
- Consider validating `on_overflow` here too (ignore invalid strategy and warn), to match frontmatter validation behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/cli/config.go
Comment on lines +130 to +163
case "context_budget.max_output_lines":
val := defaults.MaxOutputLines
source := "compiled"
if cfg.ContextBudget != nil && cfg.ContextBudget.MaxOutputLines != nil {
val = *cfg.ContextBudget.MaxOutputLines
source = "override"
}
fmt.Fprintf(cmd.OutOrStdout(), "%d (%s)\n", val, source)
case "context_budget.max_output_bytes":
val := defaults.MaxOutputBytes
source := "compiled"
if cfg.ContextBudget != nil && cfg.ContextBudget.MaxOutputBytes != nil {
val = *cfg.ContextBudget.MaxOutputBytes
source = "override"
}
fmt.Fprintf(cmd.OutOrStdout(), "%d (%s)\n", val, source)
case "context_budget.on_overflow":
val := defaults.OnOverflow
source := "compiled"
if cfg.ContextBudget != nil && cfg.ContextBudget.OnOverflow != nil {
val = *cfg.ContextBudget.OnOverflow
source = "override"
}
fmt.Fprintf(cmd.OutOrStdout(), "%s (%s)\n", val, source)
case "context_budget.eager_instructions":
val := defaults.EagerInstructions
source := "compiled"
if cfg.ContextBudget != nil && cfg.ContextBudget.EagerInstructions != nil {
val = *cfg.ContextBudget.EagerInstructions
source = "override"
}
fmt.Fprintf(cmd.OutOrStdout(), "%t (%s)\n", val, source)
default:
return fmt.Errorf("unknown field: %s (supported: model, tool_timeout)", field)
return fmt.Errorf("unknown field: %s (supported: model, tool_timeout, context_budget.max_output_lines, context_budget.max_output_bytes, context_budget.on_overflow, context_budget.eager_instructions)", field)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Config cli hides budget fields 🐞 Bug ◔ Observability

The CLI config get command does not support or print several context_budget override fields
(head_lines, tail_lines, summary_lines) even though they exist in the config schema and can be
written. This makes it difficult to verify/debug runtime override behavior for those fields.
Agent Prompt
### Issue description
`internal/cli/config.go` only supports reading/printing a subset of `context_budget.*` fields, but `pkg/config` and `pkg/config/loader` support additional fields (`head_lines`, `tail_lines`, `summary_lines`). Users can set these fields but cannot inspect effective values via `config get`.

### Issue Context
This is a CLI observability/debuggability gap: it doesn’t change runtime behavior, but it makes misconfiguration hard to detect.

### Fix Focus Areas
- internal/cli/config.go[108-231]
- internal/cli/config_test.go[39-142]
- internal/cli/config.go[11-20]
- pkg/agent/agent.go[93-103]

### Implementation notes
- Extend `cli.CompiledDefaults` to include `HeadLines`, `TailLines`, and `SummaryLines` (and optionally document them).
- Populate them in `Agent.compiledDefaults()`.
- Add `printField` + `printAllFields` cases for:
  - `context_budget.head_lines`
  - `context_budget.tail_lines`
  - `context_budget.summary_lines`
- Update the supported-field error string and add/extend tests to cover these fields.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants