Skip to content

feat(harness): per-model system prompts with engine-first discovery#227

Merged
andersonleal merged 1 commit into
mainfrom
feat/harness-per-model-prompts
Jun 4, 2026
Merged

feat(harness): per-model system prompts with engine-first discovery#227
andersonleal merged 1 commit into
mainfrom
feat/harness-per-model-prompts

Conversation

@andersonleal

@andersonleal andersonleal commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two coupled changes to the agent system prompt, following the opencode session/prompt/* architecture:

1. Remove all directory::* integration — agents learn iii from the live engine

  • The system prompt no longer injects directory skills; engine::functions::info is the mandatory API reference before every call, and the prompt teaches the full engine discovery surface (engine::functions::*, engine::workers::* incl. workers::info, engine::triggers::*, worker::* lifecycle with yes: true consent).
  • Deleted: bootstrap.ts (skills download at boot), turn-orchestrator/config.ts + system_default_skills, provisioning/load-skills.ts, the directory::skills::index/get fetchers in ports.ts.
  • agent_trigger tool description and function_not_found hints re-pointed to engine::functions::list.
  • iii-permissions.yaml: the 14 orphaned directory::* allow rules replaced with the read-only engine::* surface + worker::list; mutating worker::* ops stay approval-gated.

2. Per-model prompt variants (src/turn-orchestrator/prompt/)

Family Models Voice
anthropic.ts claude-* (default) canonical sectioned + <example> blocks
gpt.ts gpt-/o- pragmatic senior-engineer, persistence/autonomy
kimi.ts kimi-/moonshot- MUST imperatives + Ultimate Reminders
default.ts lmstudio/llamacpp local models explicit Step 1–4 procedure + final checklist

Routing reuses provider-router.decide() (prompt/index.ts, mirroring opencode's system.ts:provider()); buildSystemPrompt({override, mode, provider, model}) is threaded through provisioning. A shared invariant suite enforces the same 16-rule contract on every variant so no battle-tested rule can silently drop from one family.

Adversarially verified: instruction parity per variant against the canonical prompt, voice fidelity vs the opencode references, factual accuracy of every engine claim against the Rust handlers, and mutation-tested coverage.

Test plan

  • pnpm exec vitest run — 1103/1103 (103 prompt tests: invariant ×4 variants, routing heuristics, style anchors, assembly/mode)
  • Biome clean on touched files; typecheck has only the 2 pre-existing errors (src/index.ts:75, src/models-catalog/main.ts:8)
  • Rendered all 4 variants via bun — shared identity line, 3 examples each, zero directory::/skill residuals
  • Live smoke run per family (anthropic + one lmstudio local model) — harness serves from src via bun --watch
  • Re-run the canonical todo-worker build validation on haiku/gpt

Summary by CodeRabbit

Release Notes

  • New Features

    • Added model-specific system prompts for Anthropic, OpenAI, Kimi, and default LLM providers with tailored guidance.
    • Dynamic prompt selection based on configured provider and model.
  • Improvements

    • Refactored agent provisioning to discover available functions dynamically from live engine context.
    • Simplified system prompt assembly by removing pre-downloaded skill dependencies.
    • Enhanced function discovery guidance with direct engine queries.
  • Documentation

    • Updated architecture and provisioning documentation to reflect new discovery-based workflow.

Remove all directory::* integration from the agent loop: the system prompt
no longer injects directory skills — agents learn iii from the live engine,
with engine::functions::info as the mandatory API reference before every
call. Delete the bootstrap skill download, skills index/body fetchers, and
the system_default_skills config; re-point function_not_found hints to
engine::functions::list; allow the read-only engine::* / worker::list
surface in iii-permissions.yaml (mutating worker::* ops stay gated).

Add opencode-style per-model prompts (src/turn-orchestrator/prompt/):
anthropic (canonical sectioned voice), gpt (persistence/autonomy voice),
kimi (MUST imperatives + Ultimate Reminders), default (step-by-step for
small local models), routed by the run's provider/model through
provider-router.decide(). A shared invariant suite enforces the same
16-rule contract on every variant; routing, style, and assembly are
covered by 103 tests.
@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jun 4, 2026 6:32pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request transitions the turn-orchestrator from skill-index-based system prompts to model-specific engine-centric prompts. It removes skill bootstrapping, simplifies provisioning to build prompts from live engine context, updates permissions to use engine::* APIs, and refactors user-facing hints and documentation to guide discovery through engine functions.

Changes

Engine-centric system prompt and discovery refactoring

Layer / File(s) Summary
API boundaries and permissions update
harness/docs/architecture.md, iii-permissions.yaml, harness/src/types/provider.ts
Kernel deny list and allowlist updated: directory::* entries replaced with engine::functions::*, engine::triggers::*, engine::workers::*, engine::registered-triggers::*, and worker::list. Provider docs corrected to reference engine::functions::info instead of directory::engine::functions::info.
Model-specific system prompts and selection routing
harness/src/turn-orchestrator/prompt/anthropic.ts, harness/src/turn-orchestrator/prompt/default.ts, harness/src/turn-orchestrator/prompt/gpt.ts, harness/src/turn-orchestrator/prompt/kimi.ts, harness/src/turn-orchestrator/prompt/index.ts
Four new prompt constants (Anthropic, GPT, Kimi, Default) define unified rules for agent_trigger usage, engine function discovery, error handling, and security constraints. New index.ts module provides promptFamily routing and selectIdentityPrompt selector using provider/model to dispatch to the correct family template.
System prompt assembly refactoring
harness/src/turn-orchestrator/system-prompt.ts
buildSystemPrompt signature simplified to accept optional { override, mode, provider, model } instead of a skills array and index. Function now delegates identity-prompt selection to selectIdentityPrompt for per-model routing, optionally prepends mode paragraph, and removes all skill-index assembly logic and skill-related exports (skillIdFromUri, DefaultSkillBody, defaultSkillBody).
Provisioning ports and process simplification
harness/src/turn-orchestrator/provisioning/ports.ts, harness/src/turn-orchestrator/provisioning/process.ts
ProvisioningPorts interface stripped to only loadRunRequest and saveRunRequest (removed defaultSkillUris, fetchSkillsIndex, fetchSkillBody). createProvisioningPorts no longer accepts TurnOrchestratorConfig. processProvisioning calls buildSystemPrompt with { override, mode, provider, model } instead of fetching and processing skills data. Entire load-skills.ts and config.ts modules removed.
Orchestrator registration and wiring cleanup
harness/src/turn-orchestrator/register.ts
register function no longer loads config, calls bootstrap.run, or threads config into handlers. The function signature keeps the { configPath: string } parameter for compatibility but renames it to _ctx (unused). Removed imports for loadConfig, loadOrchestratorConfig, bootstrap.
User messaging, hints, and documentation
harness/src/turn-orchestrator/agent-trigger.ts, harness/docs/workers/turn-orchestrator.md
agent_trigger tool description clarified: payload must be a JSON object, not a JSON-encoded string. Error hints updated to direct users to engine::functions::list instead of skill-based discovery. Documentation revised to reflect engine-only system-prompt assembly at runtime from live context, removing references to config keys and bootstrap steps.
Test suite updates
harness/tests/turn-orchestrator/agent-trigger.test.ts, harness/tests/turn-orchestrator/config.test.ts, harness/tests/turn-orchestrator/provisioning-layer.test.ts, harness/tests/turn-orchestrator/provisioning.test.ts, harness/tests/turn-orchestrator/system-prompt.test.ts
Agent-trigger hint assertions updated to expect engine discovery phrasing. config.test.ts suite removed entirely (no longer tests loadOrchestratorConfig). Provisioning tests refactored: config threading removed, fakeIii simplified to stub only trigger, test expectations updated for prompt content without skills index. System-prompt tests expanded to validate contract rules across all prompt families, test promptFamily routing by { provider, model }, and verify buildSystemPrompt override/mode precedence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • iii-hq/workers#211: Both PRs modify system-prompt handling in turn-orchestrator to refocus agent guidance toward web::fetch/agent_trigger via engine discovery rather than external tool/curl invocation.

Suggested reviewers

  • sergiofilhowz
  • ytallo

Poem

🐰 The skills have fled, the engine calls!
No more the directory walls—
Per-model prompts now light the way,
Through engine::functions by the day.
Simpler wires, cleaner flows,
Where discovery and safety grow. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(harness): per-model system prompts with engine-first discovery' accurately summarizes the main changes: adding per-model system prompts and removing directory-based discovery in favor of engine-first discovery.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harness-per-model-prompts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 14 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@harness/docs/architecture.md`:
- Around line 242-244: The docs currently list approval-gated mutating worker
ops as `worker::add`, `worker::start`, `worker::stop`, `worker::remove`,
`worker::clear` but omit `worker::update`; update the text so the approval-gated
mutating-op list includes `worker::update` alongside the other `worker::*` ops
(also keep `worker::list` and `engine::registered-triggers::*` references
unchanged) to match the prompt lifecycle contract.

In `@harness/src/turn-orchestrator/prompt/anthropic.ts`:
- Around line 43-48: The rule that mandates fetching `engine::functions::info`
"before calling ANY function" conflicts with the prohibition on introspecting
discovery calls; change the rule in the prompt text that references "before any
call" so it explicitly applies only to non-discovery target functions (e.g., any
function that is not a discovery endpoint such as `engine::functions::info` or
other `engine::*`/`worker::*` discovery calls). Update the wording around the
"before any call" requirement in the `anthropic` prompt so it: 1) excludes
discovery endpoints by name (mention `engine::functions::info` as an example),
and 2) instructs callers to skip discovery-introspection and instead use `list`
results to select actual function IDs; keep the prohibition language about not
passing discovery call IDs (e.g., `engine::functions::info`) intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab32bef1-f74d-4727-9dde-d53386370c8a

📥 Commits

Reviewing files that changed from the base of the PR and between 01207fa and 857769d.

📒 Files selected for processing (22)
  • harness/docs/architecture.md
  • harness/docs/workers/turn-orchestrator.md
  • harness/src/turn-orchestrator/agent-trigger.ts
  • harness/src/turn-orchestrator/bootstrap.ts
  • harness/src/turn-orchestrator/config.ts
  • harness/src/turn-orchestrator/prompt/anthropic.ts
  • harness/src/turn-orchestrator/prompt/default.ts
  • harness/src/turn-orchestrator/prompt/gpt.ts
  • harness/src/turn-orchestrator/prompt/index.ts
  • harness/src/turn-orchestrator/prompt/kimi.ts
  • harness/src/turn-orchestrator/provisioning/load-skills.ts
  • harness/src/turn-orchestrator/provisioning/ports.ts
  • harness/src/turn-orchestrator/provisioning/process.ts
  • harness/src/turn-orchestrator/register.ts
  • harness/src/turn-orchestrator/system-prompt.ts
  • harness/src/types/provider.ts
  • harness/tests/turn-orchestrator/agent-trigger.test.ts
  • harness/tests/turn-orchestrator/config.test.ts
  • harness/tests/turn-orchestrator/provisioning-layer.test.ts
  • harness/tests/turn-orchestrator/provisioning.test.ts
  • harness/tests/turn-orchestrator/system-prompt.test.ts
  • iii-permissions.yaml
💤 Files with no reviewable changes (4)
  • harness/tests/turn-orchestrator/config.test.ts
  • harness/src/turn-orchestrator/bootstrap.ts
  • harness/src/turn-orchestrator/provisioning/load-skills.ts
  • harness/src/turn-orchestrator/config.ts

Comment on lines +242 to +244
`engine::registered-triggers::*`), and `worker::list`. Mutating
`worker::*` ops (`add`, `start`, `stop`, `remove`, `clear`) stay
approval-gated.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include worker::update in the approval-gated mutating-op list.

Line 243 omits update, but the lifecycle contract in prompt docs includes it as a mutating worker::* operation. This mismatch can mislead operators about what stays approval-gated.

Suggested docs fix
-`worker::*` ops (`add`, `start`, `stop`, `remove`, `clear`) stay
+`worker::*` ops (`add`, `start`, `stop`, `update`, `remove`, `clear`) stay
 approval-gated.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`engine::registered-triggers::*`), and `worker::list`. Mutating
`worker::*` ops (`add`, `start`, `stop`, `remove`, `clear`) stay
approval-gated.
`engine::registered-triggers::*`), and `worker::list`. Mutating
`worker::*` ops (`add`, `start`, `stop`, `update`, `remove`, `clear`) stay
approval-gated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/docs/architecture.md` around lines 242 - 244, The docs currently list
approval-gated mutating worker ops as `worker::add`, `worker::start`,
`worker::stop`, `worker::remove`, `worker::clear` but omit `worker::update`;
update the text so the approval-gated mutating-op list includes `worker::update`
alongside the other `worker::*` ops (also keep `worker::list` and
`engine::registered-triggers::*` references unchanged) to match the prompt
lifecycle contract.

Comment on lines +43 to +48
got from \`list\`. NEVER pass \`engine::functions::info\` itself or any \`engine::*\` /
\`worker::*\` discovery call as the id — that just returns
metadata ABOUT the info function (worker \`iii-engine-functions\`, no registered
triggers), which is useless and a sign you introspected the wrong thing. The discovery
calls are documented here; never introspect them. Omitting \`function_id\` errors with
\`missing field \`function_id\`\`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the conflicting contract-discovery rule.

Line 90 says to fetch engine::functions::info before calling ANY function, but Lines 43-48 forbid introspecting discovery calls. That creates an impossible first step for discovery and can cause tool-selection deadlocks. Please explicitly scope the “before any call” rule to non-discovery target functions.

Suggested wording update
-RULE 2 — BEFORE you call ANY function, fetch its contract from the engine by passing that
-function's id as `function_id` to `engine::functions::info`.
+RULE 2 — BEFORE you call any NON-DISCOVERY target function, fetch its contract from the
+engine by passing that target function's id as `function_id` to `engine::functions::info`.
+Discovery calls documented in this prompt (for example `engine::functions::list`,
+`engine::workers::*`, `engine::triggers::*`, `engine::registered-triggers::*`, `worker::list`)
+are the explicit exception and may be called directly.

Also applies to: 90-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/src/turn-orchestrator/prompt/anthropic.ts` around lines 43 - 48, The
rule that mandates fetching `engine::functions::info` "before calling ANY
function" conflicts with the prohibition on introspecting discovery calls;
change the rule in the prompt text that references "before any call" so it
explicitly applies only to non-discovery target functions (e.g., any function
that is not a discovery endpoint such as `engine::functions::info` or other
`engine::*`/`worker::*` discovery calls). Update the wording around the "before
any call" requirement in the `anthropic` prompt so it: 1) excludes discovery
endpoints by name (mention `engine::functions::info` as an example), and 2)
instructs callers to skip discovery-introspection and instead use `list` results
to select actual function IDs; keep the prohibition language about not passing
discovery call IDs (e.g., `engine::functions::info`) intact.

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