Skip to content

feat(prompt): add system prompt builder with context injection#7

Merged
hakula139 merged 32 commits intomainfrom
feat/system-prompt
Apr 5, 2026
Merged

feat(prompt): add system prompt builder with context injection#7
hakula139 merged 32 commits intomainfrom
feat/system-prompt

Conversation

@hakula139
Copy link
Copy Markdown
Owner

@hakula139 hakula139 commented Apr 5, 2026

Summary

Add a section-based system prompt builder with runtime environment detection, hierarchical instruction file discovery (CLAUDE.md / AGENTS.md), and static guidance sections. The prompt is rebuilt each turn so git status and branch info stay fresh.

The API client now sends the system prompt as an array of text blocks with the identity prefix in its own block, which is required for OAuth validation on non-Haiku models.

  • Section-based prompt assembly: identity, task guidance, caution, tool usage, tone / style, environment, user instructions.
  • CLAUDE.md / AGENTS.md discovery with root-to-CWD walk and .claude/ subdirectory support.
  • Runtime environment detection: working directory, platform, shell, git branch + clean / dirty status, local date, model name.
  • Per-turn prompt rebuild for fresh git state on every user message.
  • System prompt sent as array of TextBlock with identity prefix in its own block (OAuth requirement).
  • Research docs on system prompt architecture and API authentication (block format, attribution header, third-party restrictions).
  • User-facing guide: quickstart, configuration, instruction files.

Changes

File Description
crates/oxide-code/src/prompt.rs Module root: static guidance constants, build_system_prompt entry, assemble (testable core), find_git_root
crates/oxide-code/src/prompt/environment.rs Environment struct with detect / render, parallel git info via tokio::join!, current_date via time crate with local-offset fallback
crates/oxide-code/src/prompt/instructions.rs Slot-based instruction discovery: loadcandidate_slotswalk_root_to_cwdload_filesrender
crates/oxide-code/src/client/anthropic.rs SystemBlock type, system field changed from &str to Vec<SystemBlock>, prefix sent as separate block, SYSTEM_PROMPT_PREFIX moved here
crates/oxide-code/src/main.rs Per-turn build_system_prompt call, pass Some(&system_prompt) to client
crates/oxide-code/src/config/oauth.rs _ = style consistency
Cargo.toml Add time with local-offset feature
CLAUDE.md Add prompt.rs, prompt/environment.rs, prompt/instructions.rs to crate structure
docs/research/system-prompt.md Research: Claude Code and opencode prompt architecture, section caching, walk behavior
docs/research/anthropic-api.md Research: system block format, attribution header / fingerprint, third-party restrictions
docs/guide/quickstart.md User guide: install, credentials, first session, tools table
docs/guide/configuration.md User guide: API key, OAuth, environment variables
docs/guide/instructions.md User guide: CLAUDE.md / AGENTS.md discovery hierarchy, walk example
docs/guide/README.md Guide index
docs/README.md Add User Guide section
docs/roadmap.md Move system prompt to Working, update focus
README.md Slim down, add Documentation table linking to guide

Test plan

  • cargo build compiles cleanly
  • cargo clippy --all-targets -- -D warnings — zero warnings
  • cargo test — 195 tests pass
  • cargo llvm-cov --ignore-filename-regex 'main\.rs' — 89% line coverage (prompt modules 98–99%)
  • Live test: ox responds correctly with full system prompt (CLAUDE.md injected, environment detected)
  • Live test: main branch works, feat/system-prompt works after block-format fix

Move system prompt ownership out of the transport layer into a dedicated
prompt module. The prompt assembles static guidance sections (identity,
task, tool, style), runtime environment detection (platform, git, date),
and discovered CLAUDE.md files (global + project).
- Fix global CLAUDE.md skipped when cwd is None (candidate_paths now
  takes Option<&Path> and always includes the global path).
- Handle git command failures independently — default to empty branch
  and assume dirty rather than discarding all git info.
- Run environment detection and CLAUDE.md loading concurrently.
- Tighten test assertions (identity prefix boundary, candidate_paths
  length).
At each project location, check CLAUDE.md first, then AGENTS.md as a
fallback. The first file found at each location wins. The global
~/.claude/ directory stays CLAUDE.md only.
@hakula139 hakula139 added the enhancement New feature or request label Apr 5, 2026
@hakula139 hakula139 self-assigned this Apr 5, 2026
hakula139 added 10 commits April 5, 2026 22:35
Discover instruction files at every directory level from the project root
down to the working directory, not just at the root. Subdirectory-specific
instructions appear later in the prompt (higher priority), matching Claude
Code's hierarchical discovery behavior.
- Return GitInfo directly from detect_git_info instead of wrapping in
  Option — the function never returns None due to fallback logic, so the
  Option was misleading. The caller already gates on git_root.is_some().

- Replace date subprocess (date +%Y-%m-%d) with the time crate for
  portability and no subprocess overhead. Uses local time with UTC
  fallback when local offset detection fails.

- Improve render tests to verify exact line ordering and structure
  instead of loose contains() checks.

- Add tests for Environment::detect with various cwd/git_root
  combinations and strengthen current_date year validation.
Build the system prompt at the start of each user interaction instead
of once at startup. This keeps dynamic data (git branch, dirty status,
working directory, date) fresh during long sessions.

The rebuild happens per user message, not per tool round — one set of
git subprocess calls per interaction is acceptable overhead.
Replace Vec<(Vec<PathBuf>, &'static str)> with a named Slot struct
for readability — slot.candidates and slot.label are self-documenting
compared to tuple field access.

Add comment in walk_root_to_cwd explaining the strip_prefix behavior
when cwd equals root (empty relative path, loop body never executes).

Add tests for load_files: empty results, first-candidate preference,
AGENTS.md fallback, whitespace-only skip, multi-slot collection.
Extend candidate_slots test to verify .claude/ companion slots.
Add render_single_file test.
Add find_git_root tests: success inside a git repo (verifies .git
exists), returns None in a temp dir outside any repo.

Add build_system_prompt tests: user instructions branch (verifies
project CLAUDE.md injection), section join boundary (double newline
separator between sections).
…opencode findings

Add root-to-CWD walk pattern to CLAUDE.md Loading Hierarchy section
with a concrete example showing intermediate directory discovery.

Fix opencode section: TypeScript / Bun (not Go), correct the patterns
based on actual source investigation — provider-specific templates,
AGENTS.md / CLAUDE.md / CONTEXT.md hierarchy with walk-up semantics,
per-turn rebuild, 4-level config (not 8), two-phase compaction.

Add opencode source paths to the Sources section.
…ance

Add "Executing actions with care" section — guidance on reversibility,
blast radius, and confirmation for destructive/shared-state operations.
Inspired by Claude Code's approach but kept concise for token efficiency.

Expand task guidance: stronger "read before modify" language, specific
security vulnerability classes (command injection, path traversal,
OWASP top 10), scope discipline, and "diagnose before retry" detail.

Expand style section: output focus guidance and emoji restriction.
@hakula139 hakula139 force-pushed the feat/system-prompt branch from d717327 to 2078d13 Compare April 5, 2026 15:24
…uctions

Create docs/guide/ with three focused pages covering what actually
ships today:

- quickstart.md — install, first session, tool overview, CLAUDE.md teaser
- configuration.md — API key and OAuth auth, env vars, model selection
- instructions.md — CLAUDE.md / AGENTS.md discovery hierarchy with walk
  example, writing tips

Slim down the main README: replace inline config details with a
documentation table linking to the guide pages. Update docs/README.md
with a user guide section above the internal docs index.
@hakula139 hakula139 force-pushed the feat/system-prompt branch from 2078d13 to 1ea04c0 Compare April 5, 2026 15:25
The Anthropic API requires the identity prefix to be a separate text
block in the system array for OAuth validation. Concatenating it into
the prompt body as a single string causes 429 for non-Haiku models.

Move the prefix constant and block construction into the client, and
change stream_message to accept Option<&str> for the prompt body.
@hakula139 hakula139 merged commit 57d1b7b into main Apr 5, 2026
1 check passed
@hakula139 hakula139 deleted the feat/system-prompt branch April 5, 2026 16:27
hakula139 added a commit that referenced this pull request May 5, 2026
PR #64 (modal infrastructure) shipped Option C: bare /model opens the combined
picker, bare /effort errors with a usage hint pointing at /model. The user
guide, design notes, and roadmap still described the older "both bare forms
open the picker with different initial focus" shape. Updated:

- docs/guide/slash-commands.md — table description, mid-turn classification
  paragraph, and the "Switching the Effort" / "Switching the Model" sections.
- docs/design/slash/commands.md — design decision #5, /effort and /model
  per-command notes, source list (`agent_loop_task` → `agent_turn`).
- docs/design/slash/modals.md — design decisions #4 (`SessionInfo` →
  `LiveSessionInfo`) and #7 (typed-arg-only contract).
- docs/roadmap.md — moved the combined picker out of "Current Focus" (shipped
  in PR #64) into Working Today; replaced with the deferred /effort slider.
- CLAUDE.md — `slash/effort.rs` description updated to match the typed-arg
  contract.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant