Skip to content

Prompt node type with PromptAssembler service and graph-override layering #1049

Closed

Description

@malibio

Overview

Introduce a prompt node type that stores prompt text as first-class graph nodes, and a PromptAssembler service that composes the final agent prompt from hardcoded defaults + graph-stored overrides. This is Phase 2 of ADR-030.

Problem Statement

After #1040 improves the hardcoded prompts, they remain hardcoded in Rust. Every prompt adjustment requires a code change and rebuild. Users cannot customize agent behavior. There is no versioning, no rollback, and no semantic relationship between prompts and the knowledge they operate on.

Proposed Solution

Store prompts as nodes in the knowledge graph with a purpose-built assembler service that layers graph overrides on top of hardcoded defaults.

New node type: prompt

Properties:
  - priority: integer (assembly ordering)
  - template_syntax: "plain" | "minijinja"
  - source: "built-in" | "user-modified" | "user-created"

Content: the prompt text or template

Role (system, few-shot, tool-definition, etc.) is encoded on the relationship edge, not as a node property:

agent-config -[includes {role: "system", order: 1}]-> prompt-node

PromptAssembler service

Purpose-built service in packages/agent/ (~200-300 lines):

impl PromptAssembler {
    async fn assemble(
        &self,
        agent_config_id: &str,
        context: &TemplateContext,
    ) -> Result<AssembledPrompt>;
}

Assembly order:

  1. Load hardcoded base prompt (always present)
  2. Fetch prompt nodes connected to agent-config, ordered by priority
  3. Render Minijinja templates with context variables
  4. Concatenate in priority order

Layering model

Hardcoded defaults are always the foundation. Graph-stored prompts override and extend, never replace.

  • Cold start: empty DB works with hardcoded defaults
  • Corruption recovery: "reset to defaults" restores built-in prompts
  • Upgrade path: new hardcoded defaults ship with the app; user customizations layer on top

Caching

HashMap-based cache keyed by agent-config ID. Invalidate on node update events. Lazy rebuild on next access.

Acceptance Criteria

  • prompt node type registered with behavior and schema
  • prompt nodes get embedded (searchable via semantic search)
  • PromptAssembler service implemented in packages/agent/
  • Hardcoded base + graph overrides layering works correctly
  • Minijinja integration for {{ variable }} substitution in prompt content
  • Seed default prompt nodes created on first run (migrate content from prompt_templates.rs)
  • Cache with event-driven invalidation implemented
  • Built-in prompts can be upgraded without overwriting user modifications
  • "Reset to defaults" functionality for prompt nodes
  • Content boundary markers for user-authored content in assembled prompts
  • Tests: assembler ordering, override layering, cache invalidation, template rendering
  • Code passes bun run quality:fix
  • Tests pass (bun run test:all)

Technical Specifications

Reference Files (post-#1040)

  • PromptAssembler location: packages/agent/src/prompt_assembler.rs (new)
  • Prompt behavior: packages/core/src/behaviors/mod.rs (extend with prompt type)
  • Prompt schema: packages/core/src/schemas/ (new prompt schema)
  • Hardcoded base: packages/agent/src/local_agent/prompt_templates.rs (keep, becomes base layer)
  • Agent loop integration: packages/agent/src/local_agent/agent_loop.rs (call assembler instead of raw prompt_templates)
  • Cache invalidation: Subscribe to node update events from NodeService event channel

Minijinja Integration

  • Add minijinja crate dependency to packages/agent/Cargo.toml
  • Template context includes: current_date, model_name, workspace_context (from context_ops)
  • Template errors degrade gracefully to hardcoded base (never crash the turn)

Prompt Node Schema

{
  "node_type": "prompt",
  "properties": {
    "priority": { "type": "integer", "default": 100 },
    "template_syntax": { "type": "string", "enum": ["plain", "minijinja"], "default": "plain" },
    "source": { "type": "string", "enum": ["built-in", "user-modified", "user-created"], "default": "user-created" }
  }
}

ADR Reference

Dependencies

Non-Goals

  • Skill nodes (separate issue, Phase 3)
  • Semantic skill discovery (separate issue, Phase 4)
  • Tool whitelist scoping (part of skill nodes)
  • find_skills tool (Phase 4)

Activity

  1. self-assigned this
    on Apr 6, 2026
  2. malibio commented on Apr 6, 2026

    @malibio
    CollaboratorAuthor

    Frontend baseline: 128 test files, 3918 tests all passing (34.78s)

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions