Compact system prompt with discovery and canvas gate
apps/backend/internal/sysprompt/sysprompt.go ↗The prompt now adds canvas guidance only when IncludeCanvasGuidance is true and uses a short rich-output rule that points to tool discovery.
Canvas guidance constant
const canvasGuidanceSection = `CANVAS AUTHORING:
For a requested Kandev canvas, discover create_canvas_kandev, read_canvas_authoring_skill_kandev, and publish_canvas_kandev. Create the draft in Kandev before writing application files. Read the authoring skill once and edit only inside the returned source directory. Publish through MCP and report its release status; on failure, report the failure and do not claim publication. Files or a successful local build do not create a published Kandev canvas.
`
Compact rich output
const richOutputSection = `- show_rich_output_kandev: For a chart, graph, plot, file preview, KPI, or metrics request with data, call this now. Do not implement it as ASCII, SVG, HTML, or another app. Otherwise use prose or a small Markdown table. Get the schema and examples from tool discovery. Paths are workspace-relative; Kandev owns layout, axes, legends, and tooltips. Label series with units.
`
Capability option
type KandevContextOptions struct {
RequiresCompletionSignal bool
IncludeCoordinatorTaskControls bool
IncludeTaskTitleTool bool
IncludeCanvasGuidance bool
Autopilot bool
IncludeUserQuestionTool bool
IncludeParentQuestionTool bool
}
Selected-tools template with discovery instructions
apps/backend/config/prompts/kandev-context.md ↗The template now states the list is selected, not complete, and tells agents how to search for kandev tools.
Template header
KANDEV MCP TOOLS — Selected tools from the "kandev" server are available here.
Names ending in `_kandev` are canonical MCP protocol names. A client-specific alias may be server-qualified; use the exact callable name and schema exposed by the client.
Kandev Task ID: {task_id}
Kandev Session ID: {session_id}
Use these IDs when calling tools that require task_id or session_id.
Discovery section
MCP DISCOVERY:
These instructions list selected Kandev tools, not the complete MCP catalog.
For Kandev operations, use tools exposed by the "kandev" server. If the needed tool is already callable, use its current schema; otherwise use your client's native tool search or discovery when available.
Search for "kandev" plus the operation or known canonical tool name. If search is unavailable, inspect the MCP tools available in your client.
Use the exact callable name and schema exposed by the client. An omitted entry here does not mean that the tool is unavailable. If discovery cannot find a required tool, report that limitation before substituting another result.
Essential workflow
ESSENTIAL WORKFLOW:
Preserve task/session identity and the system marker, question barriers, title ownership, completion gates, autopilot behavior, delegation boundaries, final-action rules, and user edits in task plans. Use create/get/update plan tools. For data requests that need a chart, preview, or metric, call show_rich_output_kandev with the schema returned by discovery.
Executor profile adds canvas capability
apps/backend/internal/orchestrator/executor/executor_execute.go ↗The executor adds CapabilityCanvas only for kanban tasks when the canvases feature is enabled, so prompt and tool registration share one source.
Capability helper
func (e *Executor) withCanvasCapability(profile mcpprofile.Context) mcpprofile.Context {
if e != nil && e.canvasesEnabled && profile.Surface == mcpprofile.SurfaceKanbanTask {
return profile.WithCapability(mcpprofile.CapabilityCanvas)
}
return profile
}
Read-only profile query
func (e *Executor) ResolveTaskSessionMCPProfile(
ctx context.Context,
taskID string,
session *models.TaskSession,
allowTitleTool bool,
) (mcpprofile.Context, error) {
return e.resolveTaskSessionMCPProfile(ctx, taskID, session, allowTitleTool)
}
Orchestrator carries resolved canvas decision to launch
apps/backend/internal/orchestrator/task_operations.go ↗The orchestrator stores the admission-time canvas decision and reuses it at launch so the persisted row and the dispatched prompt agree.
New launch seam
func (s *Service) StartCreatedSessionWithPromptContextAndCanvasGuidance(
ctx context.Context,
taskID, sessionID, agentProfileID, prompt string,
skipMessageRecord, planMode, autoStart bool,
attachments []v1.MessageAttachment,
references []v1.EntityReference,
promptReferenceContext string,
canvasGuidanceResolved, includeCanvasGuidance bool,
) (*executor.TaskExecution, error) {
return s.startCreatedSession(
ctx, taskID, sessionID, agentProfileID, prompt,
skipMessageRecord, planMode, autoStart, attachments, references, promptReferenceContext,
startCreatedSessionOptions{
canvasGuidanceResolved: canvasGuidanceResolved,
includeCanvasGuidance: includeCanvasGuidance,
},
)
}
Prompt wrapper uses flag
func (s *Service) wrapCreatedSessionPrompt(
ctx context.Context,
prompt, taskID, sessionID string,
session *models.TaskSession,
dbTask *models.Task,
isOfficeTask, configMode, titleOwner, includeCanvasGuidance bool,
references []v1.EntityReference,
promptReferenceContext string,
) string {
prompt, pullRequestTargetContext := s.addTaskPullRequestTargetContext(
ctx, taskID, prompt, session.IsPassthrough,
)
referenceContext := EntityReferenceContext(references)
switch {
case session.IsPassthrough:
if !configMode && titleOwner {
return sysprompt.PendingTaskTitlePassthroughInstruction() + "\n\n" + prompt
}
return prompt
case isOfficeTask:
return sysprompt.InjectOfficeContextWithOptions(
taskID, sessionID, prompt,
s.WorkflowStepRequiresCompletionSignal(ctx, dbTask.WorkflowStepID),
referenceContext, promptReferenceContext, pullRequestTargetContext,
)
default:
return sysprompt.InjectKandevContextWithOptions(taskID, sessionID, prompt, sysprompt.KandevContextOptions{
RequiresCompletionSignal: s.WorkflowStepRequiresCompletionSignal(ctx, dbTask.WorkflowStepID),
IncludeCoordinatorTaskControls: !configMode,
IncludeTaskTitleTool: !configMode && titleOwner,
IncludeCanvasGuidance: includeCanvasGuidance,
Autopilot: dbTask.Autopilot,
IncludeUserQuestionTool: !dbTask.Autopilot && !session.IsPassthrough,
IncludeParentQuestionTool: dbTask.Autopilot && dbTask.ParentID != "",
}, referenceContext, promptReferenceContext, pullRequestTargetContext)
}
}
Message admission resolves and injects canvas guidance
apps/backend/internal/task/handlers/message_handlers.go ↗The handler resolves the canvas capability once at admission, injects it into the stored prompt, and forwards the decision to the launch seam.
Resolver
func (h *MessageHandlers) resolveCanvasGuidance(
ctx context.Context,
taskID, sessionID string,
) (bool, error) {
resolver, ok := h.orchestrator.(taskCanvasGuidanceResolver)
if !ok {
return false, nil
}
return resolver.TaskSessionCanvasGuidanceEnabled(ctx, taskID, sessionID)
}
Injection with flag
func (h *MessageHandlers) injectMessageContext(
ctx context.Context,
req wsAddMessageRequest,
sessionResp *dto.GetTaskSessionResponse,
task *models.Task,
configMode bool,
startCreatedSession bool,
titleOwner bool,
includeCanvasGuidance bool,
content string,
trustedPromptContext string,
) string {
requiresSignal := h.orchestrator != nil && h.orchestrator.StepRequiresCompletionSignal(ctx, req.TaskID)
referenceContext := orchestrator.EntityReferenceContext(req.EntityReferences)
var pullRequestTargetContext string
content, pullRequestTargetContext = sysprompt.InjectPullRequestTargetContext(
content, h.taskPullRequestTargets(ctx, task),
)
if task.IsFromOffice {
return sysprompt.InjectOfficeContextWithOptions(
req.TaskID, req.TaskSessionID, content, requiresSignal,
referenceContext, trustedPromptContext, pullRequestTargetContext,
)
}
if sessionResp.Session.IsPassthrough {
if !startCreatedSession && titleOwner {
return sysprompt.PendingTaskTitlePassthroughInstruction() + "\n\n" + content
}
return content
}
return sysprompt.InjectKandevContextWithOptions(req.TaskID, req.TaskSessionID, content, sysprompt.KandevContextOptions{
RequiresCompletionSignal: requiresSignal,
IncludeCoordinatorTaskControls: !configMode,
IncludeTaskTitleTool: !configMode && titleOwner,
IncludeCanvasGuidance: includeCanvasGuidance,
Autopilot: task.Autopilot,
IncludeUserQuestionTool: !task.Autopilot && !sessionResp.Session.IsPassthrough,
IncludeParentQuestionTool: task.Autopilot && task.ParentID != "",
}, referenceContext, trustedPromptContext, pullRequestTargetContext)
}
Canvas task preset uses discovery phrasing
apps/web/src/locales/en/canvases.json ↗The preset now tells agents to find canvas tools via discovery and to report limits instead of claiming files are a canvas.
Preset prompt
"createCanvasTaskPrompt": "Create an interactive canvas inside Kandev for the application I describe. If the application goal is missing, ask what the canvas must show or do. Find the Kandev canvas MCP tools before writing application files. If they are not callable, use native tool search for \"kandev canvas\" or inspect the available MCP catalog. Create the draft in Kandev before writing application files. Call `create_canvas_kandev` to create the draft and obtain its source directory. Read `read_canvas_authoring_skill_kandev` once without a path. Build inside the returned directory and use authorized live Kandev data for domain views. Call `publish_canvas_kandev` and address any validation errors. Report the canvas identity and whether its release is active, awaits permission review, or was unsuccessful. If publication is unsuccessful, report the failure and do not claim that the canvas is published. If workspace access requires promotion, explain the user action that is still required. A local build alone does not publish a canvas inside Kandev. If the tools remain unavailable, report the limitation instead of claiming that workspace files are a Kandev canvas."