JarvisHub开源画布原生Agent框架深度解析:长程多模态创作的状态管理与Agent协作新范式

引言

2026年8月2日,JarvisX团队正式发布了JarvisHub——一个面向长程多模态创作的开源画布原生(Canvas-Native)Agent Harness。这一项目在技术社区引发广泛关注,其核心洞察简单而深刻:现有的AI创作系统——无论是Prompt-to-Output工具、Chatbot Agent还是节点式工作流——都未能妥善解决长程创作中的项目状态管理问题

JarvisHub的论文发表于arXiv(arXiv:2607.23588),项目主页为 https://www.jarvishub.site/,源代码托管在 https://github.com/LYL1015/JarvisHub(Apache 2.0许可)。核心团队包括林云龙、林子旭、邢兆虎等贡献者,学术顾问为Tianyu Pang和Xiangyu Yue。

本文将深入解析JarvisHub的技术原理,探讨它如何通过画布即状态的设计哲学,重新定义长程多模态创作中Agent与项目状态的交互范式。


一、为什么画布原生?——创作系统的状态管理困境

1.1 长程创作的本质挑战

在真实世界中,多模态创作从来不是"一个Prompt产出一份成品"的线性过程。创作者需要:

  • 收集和整理参考素材
  • 规划布局或镜头
  • 生成多个候选方案
  • 进行局部细节修改
  • 比较不同版本
  • 整合反馈意见
  • 将中间产物组装为最终交付物

这些中间产物——Prompt、参考图、草稿、候选结果、失败尝试、版本关系、反馈——不是创作的副产品,而是创作项目的演化状态。Agent要做出下一步决策,必须知道:哪些素材已经存在?它们之间是什么关系?哪些候选被接受或拒绝?项目的哪些部分需要更新?

1.2 现有方案的局限性

方案类型代表核心局限
Prompt-to-Output工具Midjourney, DALL·E, Stable Diffusion隐藏中间决策、失败尝试、版本历史
Chatbot Agent各类LLM Agent框架线性对话难以表达空间布局、资产依赖、版本分支
节点式工作流ComfyUI, PromptChainer依赖人工预设管线,Agent无法持续检查、修复和扩展

JarvisHub的解决方案直截了当:将画布作为Agent和人类共享的项目状态。画布不仅仅是视觉界面,它是Agent可观察的外部记忆、受协议约束的动作空间、保存资产、依赖、版本和反馈的持久化状态。


二、三层核心架构深度解析

JarvisHub的架构可以用一句话概括:Canvas State是"是什么",Protocol Bridge是"能做什么"和"怎么做",Agent Runtime是"做什么"和"何时做"

┌─────────────────────────────────────────────────────────────────┐
│                    JarvisHub 三层架构总览                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   Agent Runtime                          │   │
│  │  ┌───────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐ │   │
│  │  │  Skills   │  │  Memory  │  │  Tools   │  │Subagents│ │   │
│  │  └───────────┘  └──────────┘  └──────────┘  └────────┘ │   │
│  │                                                         │   │
│  │   观察画布 → 规划动作 → 执行工具 → 返回观察结果 → 提交更新   │   │
│  └───────────────────────┬─────────────────────────────────┘   │
│                          │ 协议约束                             │
│  ┌───────────────────────▼─────────────────────────────────┐   │
│  │                   Protocol Bridge                        │   │
│  │  ┌──────────────────────────────────────────────────┐   │   │
│  │  │  Capability Manifest (Γ_t)                       │   │   │
│  │  │  Execution Grant (Ω_t)                           │   │   │
│  │  │  Action Validation → State Transition (F)        │   │   │
│  │  └──────────────────────────────────────────────────┘   │   │
│  └───────────────────────┬─────────────────────────────────┘   │
│                          │ 读写操作                             │
│  ┌───────────────────────▼─────────────────────────────────┐   │
│  │                   Canvas State                           │   │
│  │  ┌──────────────────────────────────────────────────┐   │   │
│  │  │  Artifact Graph (G_t) = (V_t, E_t)              │   │   │
│  │  │  ┌──────┐ ───边类型───→ ┌──────┐                │   │   │
│  │  │  │节点1 │  参考/版本/    │节点2 │                │   │   │
│  │  │  │      │  依赖/分组/   │      │                │   │   │
│  │  │  └──────┘  流程延续     └──────┘                │   │   │
│  │  │  X_t: 内容负载  M_t: 元数据                      │   │   │
│  │  │  U_t: 用户交互  L_t: 空间布局                    │   │   │
│  │  └──────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.1 Canvas State(画布状态层)

2.1.1 形式化定义

在轮次 $t$,JarvisHub将创作项目形式化表示为:

$$C_t = (G_t, X_t, M_t, U_t, L_t), \quad G_t = (V_t, E_t)$$

其中:

  • $G_t$:有类型资产图(Typed Artifact Graph)
  • $V_t$:画布节点集合,$|V_t| = N_t$
  • $E_t \subseteq V_t \times R \times V_t$:有向类型化边集合
  • $X_t$:可编辑内容与资产负载
  • $M_t$:溯源信息、执行元数据与运行时状态
  • $U_t$:用户选择、编辑与反馈记录
  • $L_t$:空间位置与分组布局

2.1.2 节点结构

每个节点 $v_i$ 被表示为:

$$v_i = (id_i, k_i, p_i, x_i, y_i, m_i, s_i), \quad k_i \in \mathcal{K}_{\text{node}}$$

字段含义:

字段类型说明
$id_i$稳定标识符全局唯一,可寻址引用
$k_i$节点类型文本、图片、视频、音频、UI组件、故事板等
$p_i$位置与局部布局画布坐标、尺寸、分组信息
$x_i$可编辑输入Prompt、配置字段等
$y_i$生成输出/资产句柄指向存储的资产引用
$m_i$溯源与执行诊断调用链、模型、参数、时间戳
$s_i$运行时状态已规划、运行中、已完成、失败、已接受、已拒绝

2.1.3 边类型

JarvisHub定义了五种有向边类型:

边类型语义示例
参考关系引用外部或内部素材故事板节点 → 参考图片
版本关系版本谱系候选A → 候选A_v2
生成依赖下行生成依赖分镜 → 视频片段
分组关系逻辑分组场景1 → 镜头1, 镜头2, 镜头3
流程延续流程控制故事板完成 → 进入生成阶段

2.1.4 三个关键特性

  1. 可寻址性(Addressable):Agent可以精确引用某个候选图片、视频片段、网页渲染或幻灯片,而不是依赖模糊的对话指代。
  2. 可复用性(Reusable):参考素材、草稿、被拒绝的候选、中间结果都可以在后续步骤中作为输入。
  3. 可检查性(Inspectable):用户和Agent可以追溯:哪些素材影响了结果?哪个版本被选中?哪些下游资产依赖它?

2.1.5 代码实现:画布状态管理

以下是Go语言实现的画布状态核心数据结构,展示了JarvisHub中画布状态的实际组织方式:

// canvas/state.go — Canvas State 核心实现

package canvas

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

// NodeKind 定义画布节点类型
type NodeKind string

const (
	NodeKindText        NodeKind = "text"
	NodeKindImage       NodeKind = "image"
	NodeKindVideo       NodeKind = "video"
	NodeKindAudio       NodeKind = "audio"
	NodeKindStoryboard  NodeKind = "storyboard"
	NodeKindWebPage     NodeKind = "webpage"
	NodeKindSlide       NodeKind = "slide"
	NodeKindCharacter   NodeKind = "character"
	NodeKindScene       NodeKind = "scene"
	NodeKindReference   NodeKind = "reference"
	NodeKindCandidate   NodeKind = "candidate"
	NodeKindFeedback    NodeKind = "feedback"
)

// RuntimeStatus 节点运行时状态
type RuntimeStatus string

const (
	StatusPlanned   RuntimeStatus = "planned"
	StatusRunning   RuntimeStatus = "running"
	StatusCompleted RuntimeStatus = "completed"
	StatusFailed    RuntimeStatus = "failed"
	StatusAccepted  RuntimeStatus = "accepted"
	StatusRejected  RuntimeStatus = "rejected"
	StatusPendingReview RuntimeStatus = "pending_review"
)

// EdgeKind 边类型
type EdgeKind string

const (
	EdgeReference    EdgeKind = "reference"
	EdgeVersion      EdgeKind = "version"
	EdgeDependency   EdgeKind = "dependency"
	EdgeGroup        EdgeKind = "group"
	EdgeContinuation EdgeKind = "continuation"
)

// Node 画布节点
type Node struct {
	ID          string                 `json:"id"`
	Kind        NodeKind               `json:"kind"`
	Position    Position               `json:"position"`
	Input       map[string]interface{} `json:"input,omitempty"`
	Output      map[string]interface{} `json:"output,omitempty"`
	Metadata    Metadata               `json:"metadata"`
	Status      RuntimeStatus          `json:"status"`
	CreatedAt   time.Time              `json:"created_at"`
	UpdatedAt   time.Time              `json:"updated_at"`
}

// Position 节点位置
type Position struct {
	X       float64 `json:"x"`
	Y       float64 `json:"y"`
	Width   float64 `json:"width,omitempty"`
	Height  float64 `json:"height,omitempty"`
	GroupID string  `json:"group_id,omitempty"`
}

// Metadata 溯源与执行元数据
type Metadata struct {
	Provenance   string            `json:"provenance,omitempty"`
	ModelName    string            `json:"model_name,omitempty"`
	ModelParams  map[string]string `json:"model_params,omitempty"`
	ToolCallID   string            `json:"tool_call_id,omitempty"`
	ErrorMessage string            `json:"error_message,omitempty"`
	Duration     time.Duration     `json:"duration,omitempty"`
	Version      int               `json:"version"`
}

// Edge 有向边
type Edge struct {
	SourceID string   `json:"source_id"`
	TargetID string   `json:"target_id"`
	Kind     EdgeKind `json:"kind"`
	Label    string   `json:"label,omitempty"`
}

// CanvasState 画布状态
type CanvasState struct {
	mu       sync.RWMutex
	Nodes    map[string]*Node   `json:"nodes"`
	Edges    []Edge             `json:"edges"`
	UserData map[string]interface{} `json:"user_data,omitempty"`
	Version  int                `json:"version"`
}

// NewCanvasState 创建新的画布状态
func NewCanvasState() *CanvasState {
	return &CanvasState{
		Nodes:    make(map[string]*Node),
		Edges:    make([]Edge, 0),
		UserData: make(map[string]interface{}),
		Version:  0,
	}
}

// AddNode 添加节点
func (cs *CanvasState) AddNode(n *Node) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	if _, exists := cs.Nodes[n.ID]; exists {
		return fmt.Errorf("node %s already exists", n.ID)
	}
	n.CreatedAt = time.Now()
	n.UpdatedAt = time.Now()
	cs.Nodes[n.ID] = n
	cs.Version++
	return nil
}

// GetNode 获取节点
func (cs *CanvasState) GetNode(id string) (*Node, error) {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	n, exists := cs.Nodes[id]
	if !exists {
		return nil, fmt.Errorf("node %s not found", id)
	}
	return n, nil
}

// UpdateNode 更新节点内容
func (cs *CanvasState) UpdateNode(id string, updateFn func(*Node)) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	n, exists := cs.Nodes[id]
	if !exists {
		return fmt.Errorf("node %s not found", id)
	}
	updateFn(n)
	n.UpdatedAt = time.Now()
	cs.Version++
	return nil
}

// AddEdge 添加边
func (cs *CanvasState) AddEdge(sourceID, targetID string, kind EdgeKind, label string) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	if _, ok := cs.Nodes[sourceID]; !ok {
		return fmt.Errorf("source node %s not found", sourceID)
	}
	if _, ok := cs.Nodes[targetID]; !ok {
		return fmt.Errorf("target node %s not found", targetID)
	}

	cs.Edges = append(cs.Edges, Edge{
		SourceID: sourceID,
		TargetID: targetID,
		Kind:     kind,
		Label:    label,
	})
	cs.Version++
	return nil
}

// GetDependents 获取依赖于指定节点的所有下游节点
func (cs *CanvasState) GetDependents(nodeID string) []*Node {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	var dependents []*Node
	for _, e := range cs.Edges {
		if e.SourceID == nodeID && e.Kind == EdgeDependency {
			if n, ok := cs.Nodes[e.TargetID]; ok {
				dependents = append(dependents, n)
			}
		}
	}
	return dependents
}

// GetUpstreamNodes 获取指定节点的所有上游依赖
func (cs *CanvasState) GetUpstreamNodes(nodeID string) []*Node {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	var upstream []*Node
	for _, e := range cs.Edges {
		if e.TargetID == nodeID && e.Kind == EdgeDependency {
			if n, ok := cs.Nodes[e.SourceID]; ok {
				upstream = append(upstream, n)
			}
		}
	}
	return upstream
}

// Snapshot 创建画布快照(用于检查点/恢复)
func (cs *CanvasState) Snapshot() *CanvasState {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	snap := &CanvasState{
		Nodes:    make(map[string]*Node),
		Edges:    make([]Edge, len(cs.Edges)),
		UserData: make(map[string]interface{}),
		Version:  cs.Version,
	}
	for k, v := range cs.Nodes {
		cp := *v
		snap.Nodes[k] = &cp
	}
	copy(snap.Edges, cs.Edges)
	for k, v := range cs.UserData {
		snap.UserData[k] = v
	}
	return snap
}
# canvas_state.py — Python 画布状态实现

from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional
from datetime import datetime
import copy
import threading


class NodeKind(str, Enum):
    TEXT = "text"
    IMAGE = "image"
    VIDEO = "video"
    AUDIO = "audio"
    STORYBOARD = "storyboard"
    WEBPAGE = "webpage"
    SLIDE = "slide"
    CHARACTER = "character"
    SCENE = "scene"
    REFERENCE = "reference"
    CANDIDATE = "candidate"
    FEEDBACK = "feedback"


class RuntimeStatus(str, Enum):
    PLANNED = "planned"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    ACCEPTED = "accepted"
    REJECTED = "rejected"
    PENDING_REVIEW = "pending_review"


class EdgeKind(str, Enum):
    REFERENCE = "reference"
    VERSION = "version"
    DEPENDENCY = "dependency"
    GROUP = "group"
    CONTINUATION = "continuation"


@dataclass
class Position:
    x: float
    y: float
    width: Optional[float] = None
    height: Optional[float] = None
    group_id: Optional[str] = None


@dataclass
class Metadata:
    provenance: Optional[str] = None
    model_name: Optional[str] = None
    model_params: Dict[str, str] = field(default_factory=dict)
    tool_call_id: Optional[str] = None
    error_message: Optional[str] = None
    duration_ms: Optional[float] = None
    version: int = 1


@dataclass
class Node:
    id: str
    kind: NodeKind
    position: Position
    input: Dict[str, Any] = field(default_factory=dict)
    output: Dict[str, Any] = field(default_factory=dict)
    metadata: Metadata = field(default_factory=Metadata)
    status: RuntimeStatus = RuntimeStatus.PLANNED
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    def __post_init__(self):
        now = datetime.now()
        self.created_at = self.created_at or now
        self.updated_at = self.updated_at or now


@dataclass
class Edge:
    source_id: str
    target_id: str
    kind: EdgeKind
    label: Optional[str] = None


class CanvasState:
    """画布状态 —— 线程安全的有类型资产图"""

    def __init__(self):
        self._lock = threading.RLock()
        self.nodes: Dict[str, Node] = {}
        self.edges: List[Edge] = []
        self.user_data: Dict[str, Any] = {}
        self.version: int = 0

    def add_node(self, node: Node) -> None:
        with self._lock:
            if node.id in self.nodes:
                raise ValueError(f"Node {node.id} already exists")
            node.created_at = datetime.now()
            node.updated_at = datetime.now()
            self.nodes[node.id] = node
            self.version += 1

    def get_node(self, node_id: str) -> Optional[Node]:
        with self._lock:
            return self.nodes.get(node_id)

    def update_node(self, node_id: str, **updates) -> None:
        with self._lock:
            node = self.nodes.get(node_id)
            if node is None:
                raise KeyError(f"Node {node_id} not found")
            for key, value in updates.items():
                if hasattr(node, key):
                    setattr(node, key, value)
            node.updated_at = datetime.now()
            self.version += 1

    def add_edge(self, source_id: str, target_id: str,
                 kind: EdgeKind, label: Optional[str] = None) -> None:
        with self._lock:
            if source_id not in self.nodes:
                raise KeyError(f"Source node {source_id} not found")
            if target_id not in self.nodes:
                raise KeyError(f"Target node {target_id} not found")
            self.edges.append(Edge(source_id, target_id, kind, label))
            self.version += 1

    def get_dependents(self, node_id: str) -> List[Node]:
        """获取依赖指定节点的所有下游节点"""
        with self._lock:
            return [
                self.nodes[e.target_id]
                for e in self.edges
                if e.source_id == node_id and e.kind == EdgeKind.DEPENDENCY
                and e.target_id in self.nodes
            ]

    def get_upstream(self, node_id: str) -> List[Node]:
        """获取指定节点的所有上游依赖"""
        with self._lock:
            return [
                self.nodes[e.source_id]
                for e in self.edges
                if e.target_id == node_id and e.kind == EdgeKind.DEPENDENCY
                and e.source_id in self.nodes
            ]

    def get_nodes_by_status(self, status: RuntimeStatus) -> List[Node]:
        with self._lock:
            return [n for n in self.nodes.values() if n.status == status]

    def get_nodes_by_kind(self, kind: NodeKind) -> List[Node]:
        with self._lock:
            return [n for n in self.nodes.values() if n.kind == kind]

    def snapshot(self) -> 'CanvasState':
        """创建不可变快照用于检查点恢复"""
        with self._lock:
            snap = CanvasState()
            snap.nodes = copy.deepcopy(self.nodes)
            snap.edges = copy.deepcopy(self.edges)
            snap.user_data = copy.deepcopy(self.user_data)
            snap.version = self.version
            return snap

2.2 Protocol Bridge(协议桥层)

Protocol Bridge是JarvisHub中最具创新性的设计之一。它充当Agent与画布之间的契约层,确保每一轮交互都是显式、可验证、可恢复的。

2.2.1 能力清单(Capability Manifest)

在轮次 $t$,协议桥向Agent提供能力清单 $\Gamma_t$,包含:

  • 当前项目中可用的节点类型(文本、图片、视频、音频、UI组件等)
  • 允许的变更操作(创建、更新、删除、连接、分组、选择、分支)
  • 可调用的工具集(Canvas Tools、Generation Tools、Native Tools等)
  • 可访问的资产句柄(已有资产引用)

2.2.2 执行授权(Execution Grant)

协议桥进一步推导出执行授权 $\Omega_t$,限制Agent在当前轮次中可以执行的操作范围。授权机制确保:

  • Agent不能执行未授权的操作
  • 每次画布修改都经过校验
  • 所有操作都被记录在轨迹中

2.2.3 状态转换函数

在反馈信号 $f_t$ 和修复决策 $r_t$ 的参与下,画布状态通过以下状态转换函数演化:

$$C_{t+1} = \mathcal{F}(C_t, a_t, o_t, f_t, r_t)$$

其中 $\mathcal{F}$ 是协议桥实现的状态转换算子,负责:

  • 写入接受的动作和返回的证据
  • 创建、更新或删除节点
  • 连接依赖关系
  • 附加资产
  • 记录失败
  • 创建检查点
  • 请求人工修正

2.2.4 代码实现:Protocol Bridge

// bridge/protocol.go — Protocol Bridge 核心实现

package bridge

import (
	"errors"
	"fmt"
	"time"

	"jarvishub/canvas"
)

// OperationType 画布操作类型
type OperationType string

const (
	OpCreateNode  OperationType = "create_node"
	OpUpdateNode  OperationType = "update_node"
	OpDeleteNode  OperationType = "delete_node"
	OpConnectEdge OperationType = "connect_edge"
	OpGroupNodes  OperationType = "group_nodes"
	OpSelectNode  OperationType = "select_node"
	OpBranchNode  OperationType = "branch_node"
)

// ToolFamily 工具族
type ToolFamily string

const (
	ToolCanvas     ToolFamily = "canvas"
	ToolGeneration ToolFamily = "generation"
	ToolNative     ToolFamily = "native"
	ToolRecovery   ToolFamily = "recovery"
	ToolMCP        ToolFamily = "mcp"
)

// CapabilityManifest 能力清单
type CapabilityManifest struct {
	AllowedNodeKinds []canvas.NodeKind   `json:"allowed_node_kinds"`
	AllowedOps       []OperationType     `json:"allowed_ops"`
	AvailableTools   []ToolFamily        `json:"available_tools"`
	ArtifactHandles  []string            `json:"artifact_handles"`
	Constraints      map[string]string   `json:"constraints,omitempty"`
}

// ExecutionGrant 执行授权
type ExecutionGrant struct {
	MaxNodes       int              `json:"max_nodes,omitempty"`
	AllowedActions []OperationType  `json:"allowed_actions"`
	ToolGrants     map[ToolFamily]bool `json:"tool_grants"`
	MaxTokens      int              `json:"max_tokens,omitempty"`
	TimeoutMs      int64            `json:"timeout_ms,omitempty"`
}

// Action 代理动作
type Action struct {
	Type      OperationType          `json:"type"`
	Payload   map[string]interface{} `json:"payload"`
	ToolCalls []ToolCall             `json:"tool_calls,omitempty"`
	Timestamp time.Time              `json:"timestamp"`
}

// ToolCall 工具调用
type ToolCall struct {
	Family    ToolFamily             `json:"family"`
	Name      string                 `json:"name"`
	Params    map[string]interface{} `json:"params"`
	RequestID string                 `json:"request_id"`
}

// ToolObservation 工具观察结果
type ToolObservation struct {
	ToolCallID string                 `json:"tool_call_id"`
	Success    bool                   `json:"success"`
	Output     map[string]interface{} `json:"output,omitempty"`
	Artifacts  []string               `json:"artifacts,omitempty"`
	Error      string                 `json:"error,omitempty"`
	DurationMs int64                  `json:"duration_ms"`
}

// FeedbackSignal 反馈信号
type FeedbackSignal struct {
	Source    string                 `json:"source"` // "human", "model_critic", "subagent", "evaluator"
	NodeID    string                 `json:"node_id,omitempty"`
	Verdict   string                 `json:"verdict"` // "accept", "reject", "partial", "clarify"
	Comments  string                 `json:"comments,omitempty"`
	Timestamp time.Time              `json:"timestamp"`
	Edits     map[string]interface{} `json:"edits,omitempty"`
}

// RepairDecision 修复决策
type RepairDecision struct {
	Action      string                 `json:"action"` // "continue", "repair", "clarify", "stop"
	TargetNodes []string               `json:"target_nodes,omitempty"`
	Strategy    string                 `json:"strategy,omitempty"`
	Params      map[string]interface{} `json:"params,omitempty"`
}

// StateTransition 状态转换记录
type StateTransition struct {
	Turn        int                `json:"turn"`
	BeforeState *canvas.CanvasState `json:"before_state"`
	Action      *Action            `json:"action"`
	Observation *ToolObservation   `json:"observation,omitempty"`
	Feedback    *FeedbackSignal    `json:"feedback,omitempty"`
	Repair      *RepairDecision    `json:"repair,omitempty"`
	AfterState  *canvas.CanvasState `json:"after_state"`
	Timestamp   time.Time          `json:"timestamp"`
}

// ProtocolBridge 协议桥
type ProtocolBridge struct {
	state       *canvas.CanvasState
	manifest    *CapabilityManifest
	grant       *ExecutionGrant
	transitions []StateTransition
	turnCount   int
}

// NewProtocolBridge 创建协议桥
func NewProtocolBridge(state *canvas.CanvasState) *ProtocolBridge {
	return &ProtocolBridge{
		state:       state,
		transitions: make([]StateTransition, 0),
	}
}

// BuildManifest 构建当前轮次的能力清单
func (pb *ProtocolBridge) BuildManifest(projectType string) *CapabilityManifest {
	// 根据项目类型动态构建清单
	manifest := &CapabilityManifest{
		AllowedOps: []OperationType{
			OpCreateNode, OpUpdateNode, OpConnectEdge,
		},
		AvailableTools: []ToolFamily{
			ToolCanvas, ToolGeneration, ToolNative, ToolRecovery,
		},
		ArtifactHandles: make([]string, 0),
	}

	// 从画布状态中收集已有资产
	for id := range pb.state.Nodes {
		manifest.ArtifactHandles = append(manifest.ArtifactHandles, id)
	}

	// 项目类型特定配置
	switch projectType {
	case "narrative_media":
		manifest.AllowedNodeKinds = []canvas.NodeKind{
			canvas.NodeKindText, canvas.NodeKindImage,
			canvas.NodeKindVideo, canvas.NodeKindAudio,
			canvas.NodeKindStoryboard, canvas.NodeKindCharacter,
			canvas.NodeKindScene, canvas.NodeKindReference,
			canvas.NodeKindCandidate,
		}
	case "web_development":
		manifest.AllowedNodeKinds = []canvas.NodeKind{
			canvas.NodeKindText, canvas.NodeKindImage,
			canvas.NodeKindWebPage, canvas.NodeKindReference,
			canvas.NodeKindCandidate,
		}
		manifest.AvailableTools = append(manifest.AvailableTools, ToolMCP)
	case "presentation":
		manifest.AllowedNodeKinds = []canvas.NodeKind{
			canvas.NodeKindText, canvas.NodeKindImage,
			canvas.NodeKindSlide, canvas.NodeKindReference,
			canvas.NodeKindCandidate,
		}
	}

	pb.manifest = manifest
	return manifest
}

// DeriveGrant 推导执行授权
func (pb *ProtocolBridge) DeriveGrant(userQuery string) *ExecutionGrant {
	// 根据用户查询和当前画布状态推导授权
	grant := &ExecutionGrant{
		MaxNodes: 100,
		AllowedActions: []OperationType{
			OpCreateNode, OpUpdateNode, OpConnectEdge,
		},
		ToolGrants: map[ToolFamily]bool{
			ToolCanvas:     true,
			ToolGeneration: true,
			ToolNative:     true,
			ToolRecovery:   true,
		},
		TimeoutMs: 300000, // 5分钟
	}
	pb.grant = grant
	return grant
}

// ValidateAction 校验动作是否合法
func (pb *ProtocolBridge) ValidateAction(action *Action) error {
	// 检查操作类型是否被授权
	allowed := false
	for _, op := range pb.grant.AllowedActions {
		if op == action.Type {
			allowed = true
			break
		}
	}
	if !allowed {
		return fmt.Errorf("operation %s is not in current execution grant", action.Type)
	}

	// 检查工具调用是否被授权
	for _, tc := range action.ToolCalls {
		if granted, ok := pb.grant.ToolGrants[tc.Family]; !ok || !granted {
			return fmt.Errorf("tool family %s is not granted", tc.Family)
		}
	}

	return nil
}

// CommitTransition 提交状态转换并记录轨迹
func (pb *ProtocolBridge) CommitTransition(
	before *canvas.CanvasState,
	action *Action,
	observation *ToolObservation,
	feedback *FeedbackSignal,
	repair *RepairDecision,
	after *canvas.CanvasState,
) StateTransition {
	pb.turnCount++
	transition := StateTransition{
		Turn:        pb.turnCount,
		BeforeState: before,
		Action:      action,
		Observation: observation,
		Feedback:    feedback,
		Repair:      repair,
		AfterState:  after,
		Timestamp:   time.Now(),
	}
	pb.transitions = append(pb.transitions, transition)
	return transition
}

// GetTrajectory 获取完整执行轨迹
func (pb *ProtocolBridge) GetTrajectory() []StateTransition {
	return pb.transitions
}

2.3 Agent Runtime(代理运行时层)

Agent Runtime是JarvisHub的执行引擎,负责将用户请求转化为经过协议检查的画布更新。

2.3.1 运行时循环

每一轮的核心循环:

观察画布 → 解读用户输入 → 选择许可动作 → 调用能力 → 返回观察结果 → 写入画布

2.3.2 五类工具族

工具族运行时角色代表操作
Canvas Tools更新项目状态读、创建、更新、连接、分组、选择、分支
Generation Tools生成画布资产图片、视频、音频、组合媒体生成
Native Tools使用外部执行环境浏览器、文件、代码、搜索、文档、PPT操作
Recovery Tools检查与修复结构化反馈、验证、检查点、局部修复
MCP Tools扩展外部服务MCP协议下的能力

2.3.3 三个高级支持机制

  1. Skills(技能):可复用的创作流程模板,如故事板、参考引导生成、网页重建、视频Prompting、PPT构建。
  2. Memory(记忆):跨轮次保存用户偏好、历史决策和过程知识。
  3. Subagents(子代理):处理可并行执行的独立子任务,主Agent整合有效结果。

2.3.4 代码实现:Agent Runtime

# runtime/agent_runtime.py — Agent Runtime 核心实现

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable, Dict, List, Optional, Any
from enum import Enum
import time
import logging

from canvas_state import CanvasState, Node, NodeKind, RuntimeStatus, EdgeKind
from protocol_bridge import (ProtocolBridge, CapabilityManifest, ExecutionGrant,
                             Action, OperationType, ToolCall, ToolObservation,
                             FeedbackSignal, RepairDecision, ToolFamily)


logger = logging.getLogger(__name__)


@dataclass
class RuntimeContext:
    """运行时上下文"""
    user_query: str
    canvas_state: CanvasState
    manifest: CapabilityManifest
    grant: ExecutionGrant
    skills: Dict[str, 'Skill']
    memory: Dict[str, Any]
    subagents: List['SubAgent']


class AgentRuntime:
    """Agent执行运行时"""

    def __init__(self, bridge: ProtocolBridge, state: CanvasState):
        self.bridge = bridge
        self.state = state
        self.skills: Dict[str, Skill] = {}
        self.memory: Dict[str, Any] = {}
        self.subagents: List[SubAgent] = []
        self.tool_handlers: Dict[ToolFamily, ToolHandler] = {}
        self.turn_count = 0

    def register_tool_handler(self, family: ToolFamily, handler: ToolHandler):
        self.tool_handlers[family] = handler

    def register_skill(self, name: str, skill: Skill):
        self.skills[name] = skill

    def execute_turn(self, user_query: str) -> Dict[str, Any]:
        """执行一轮运行时循环"""
        self.turn_count += 1
        logger.info(f"=== Turn {self.turn_count} ===")

        # 步骤1: 构建能力清单
        manifest = self._build_manifest()

        # 步骤2: 推导执行授权
        grant = self._derive_grant(user_query)

        # 步骤3: 创建运行时上下文
        ctx = RuntimeContext(
            user_query=user_query,
            canvas_state=self.state.snapshot(),
            manifest=manifest,
            grant=grant,
            skills=self.skills,
            memory=self.memory,
            subagents=self.subagents,
        )

        # 步骤4: 选择动作(由LLM驱动,这里模拟)
        action = self._select_action(ctx)

        # 步骤5: 校验动作
        validation_error = self._validate_action(action, grant)
        if validation_error:
            return {"error": validation_error, "turn": self.turn_count}

        # 步骤6: 执行动作
        before_state = self.state.snapshot()
        observation = self._execute_action(action)

        # 步骤7: 接收反馈
        feedback = self._collect_feedback(observation, ctx)

        # 步骤8: 确定修复决策
        repair = self._decide_repair(feedback, before_state)

        # 步骤9: 执行修复
        if repair and repair.action == "repair":
            self._apply_repair(repair, before_state)
        elif repair and repair.action == "stop":
            logger.warning("Agent stopped due to insufficient evidence")
            return {"status": "stopped", "reason": repair.strategy}

        # 步骤10: 提交状态转换
        after_state = self.state.snapshot()
        transition = self.bridge.commit_transition(
            before=before_state,
            action=action,
            observation=observation,
            feedback=feedback,
            repair=repair,
            after=after_state,
        )

        return {
            "turn": self.turn_count,
            "action": action,
            "observation": observation,
            "feedback": feedback,
            "repair": repair,
            "transition_recorded": True,
        }

    def _build_manifest(self) -> CapabilityManifest:
        project_type = self.memory.get("project_type", "general")
        return self.bridge.build_manifest(project_type)

    def _derive_grant(self, query: str) -> ExecutionGrant:
        return self.bridge.derive_grant(query)

    def _select_action(self, ctx: RuntimeContext) -> Action:
        """
        选择动作。在实际系统中由LLM Agent驱动,
        这里展示核心逻辑结构。
        """
        # 1. 检查是否有匹配的Skill
        for skill_name, skill in self.skills.items():
            if skill.should_apply(ctx):
                logger.info(f"Applying skill: {skill_name}")
                return skill.generate_action(ctx)

        # 2. 默认:创建新节点并调用生成工具
        return Action(
            type=OperationType.CREATE_NODE,
            payload={
                "kind": "image",
                "prompt": ctx.user_query,
            },
            tool_calls=[
                ToolCall(
                    family=ToolFamily.GENERATION,
                    name="generate_image",
                    params={"prompt": ctx.user_query},
                    request_id=f"req_{self.turn_count}",
                )
            ],
        )

    def _validate_action(self, action: Action, grant: ExecutionGrant) -> Optional[str]:
        try:
            return self.bridge.validate_action(action)
        except Exception as e:
            return str(e)

    def _execute_action(self, action: Action) -> ToolObservation:
        """执行动作并返回观察结果"""
        start = time.time()

        all_results = []
        for tc in action.tool_calls:
            handler = self.tool_handlers.get(tc.family)
            if handler is None:
                return ToolObservation(
                    tool_call_id=tc.request_id,
                    success=False,
                    error=f"No handler for tool family: {tc.family}",
                    duration_ms=int((time.time() - start) * 1000),
                )
            try:
                result = handler.execute(tc)
                all_results.append(result)
            except Exception as e:
                all_results.append(ToolObservation(
                    tool_call_id=tc.request_id,
                    success=False,
                    error=str(e),
                    duration_ms=int((time.time() - start) * 1000),
                ))

        # 如果是创建节点的操作,将结果写入画布
        if action.type == OperationType.CREATE_NODE and all_results:
            result = all_results[0]
            if result.success:
                node = Node(
                    id=f"node_{self.turn_count}_{int(time.time())}",
                    kind=NodeKind(action.payload.get("kind", "text")),
                    position={"x": 100, "y": 100 * self.turn_count},
                    input=action.payload,
                    output=result.output or {},
                    status=RuntimeStatus.COMPLETED,
                )
                self.state.add_node(node)

        # 合并观察结果
        merged = ToolObservation(
            tool_call_id=action.tool_calls[0].request_id if action.tool_calls else "",
            success=all(r.success for r in all_results),
            output={},
            artifacts=[],
            duration_ms=int((time.time() - start) * 1000),
        )
        for r in all_results:
            if r.output:
                merged.output.update(r.output)
            if r.artifacts:
                merged.artifacts.extend(r.artifacts)

        return merged

    def _collect_feedback(self, obs: ToolObservation, ctx: RuntimeContext) -> Optional[FeedbackSignal]:
        """收集反馈信号(来自用户、模型评估器或子Agent)"""
        if not obs.success:
            return FeedbackSignal(
                source="evaluator",
                verdict="reject",
                comments=f"Tool execution failed: {obs.error}",
            )
        return None

    def _decide_repair(self, feedback: Optional[FeedbackSignal],
                       state: CanvasState) -> Optional[RepairDecision]:
        """根据反馈决定修复策略"""
        if feedback is None:
            return RepairDecision(action="continue")

        if feedback.verdict == "reject":
            # 查找失败的节点
            failed_nodes = state.get_nodes_by_status(RuntimeStatus.FAILED)
            if failed_nodes:
                return RepairDecision(
                    action="repair",
                    target_nodes=[n.id for n in failed_nodes],
                    strategy="regenerate_with_feedback",
                    params={"feedback": feedback.comments},
                )
            return RepairDecision(action="clarify", strategy="ask_user_for_guidance")

        return RepairDecision(action="continue")

    def _apply_repair(self, repair: RepairDecision, state: CanvasState):
        """应用修复决策"""
        for node_id in repair.target_nodes:
            node = state.get_node(node_id)
            if node:
                # 标记为待修复,重新规划
                state.update_node(node_id, status=RuntimeStatus.PLANNED)
                logger.info(f"Repair scheduled for node {node_id}: {repair.strategy}")
// runtime/runtime.go — Agent Runtime Go实现

package runtime

import (
	"context"
	"fmt"
	"log"
	"time"

	"jarvishub/bridge"
	"jarvishub/canvas"
)

// ToolHandler 工具处理函数
type ToolHandler func(ctx context.Context, call bridge.ToolCall) (*bridge.ToolObservation, error)

// RuntimeConfig 运行时配置
type RuntimeConfig struct {
	MaxTurns       int
	DefaultTimeout time.Duration
	ModelName      string
}

// AgentRuntime 代理运行时
type AgentRuntime struct {
	config       RuntimeConfig
	bridge       *bridge.ProtocolBridge
	state        *canvas.CanvasState
	skills       map[string]Skill
	memory       map[string]interface{}
	toolHandlers map[bridge.ToolFamily]ToolHandler
	turnCount    int
}

// Skill 技能接口
type Skill interface {
	Name() string
	ShouldApply(ctx context.Context, query string, state *canvas.CanvasState) bool
	Execute(ctx context.Context, query string, state *canvas.CanvasState) (*bridge.Action, error)
}

// NewAgentRuntime 创建Agent运行时
func NewAgentRuntime(config RuntimeConfig, bridge *bridge.ProtocolBridge, state *canvas.CanvasState) *AgentRuntime {
	return &AgentRuntime{
		config:       config,
		bridge:       bridge,
		state:        state,
		skills:       make(map[string]Skill),
		memory:       make(map[string]interface{}),
		toolHandlers: make(map[bridge.ToolFamily]ToolHandler),
	}
}

// RegisterToolHandler 注册工具处理函数
func (rt *AgentRuntime) RegisterToolHandler(family bridge.ToolFamily, handler ToolHandler) {
	rt.toolHandlers[family] = handler
}

// RegisterSkill 注册技能
func (rt *AgentRuntime) RegisterSkill(skill Skill) {
	rt.skills[skill.Name()] = skill
}

// ExecuteTurn 执行一轮运行时循环
func (rt *AgentRuntime) ExecuteTurn(ctx context.Context, userQuery string) (map[string]interface{}, error) {
	rt.turnCount++
	log.Printf("=== Turn %d ===\n", rt.turnCount)

	// 1. 构建能力清单
	projectType := "general"
	if pt, ok := rt.memory["project_type"].(string); ok {
		projectType = pt
	}
	manifest := rt.bridge.BuildManifest(projectType)

	// 2. 推导执行授权
	grant := rt.bridge.DeriveGrant(userQuery)

	// 3. 选择动作
	action, err := rt.selectAction(ctx, userQuery, manifest, grant)
	if err != nil {
		return nil, fmt.Errorf("action selection failed: %w", err)
	}

	// 4. 校验动作
	if err := rt.bridge.ValidateAction(action); err != nil {
		return nil, fmt.Errorf("action validation failed: %w", err)
	}

	// 5. 执行动作
	beforeState := rt.state.Snapshot()
	observation, err := rt.executeAction(ctx, action)
	if err != nil {
		observation = &bridge.ToolObservation{
			ToolCallID: action.ToolCalls[0].RequestID,
			Success:    false,
			Error:      err.Error(),
		}
	}

	// 6. 收集反馈
	feedback := rt.collectFeedback(observation)

	// 7. 决定修复策略
	repair := rt.decideRepair(feedback, beforeState)

	// 8. 应用修复
	if repair != nil && repair.Action == "repair" {
		rt.applyRepair(ctx, repair, beforeState)
	}

	// 9. 提交状态转换
	afterState := rt.state.Snapshot()
	rt.bridge.CommitTransition(beforeState, action, observation, feedback, repair, afterState)

	return map[string]interface{}{
		"turn":       rt.turnCount,
		"action":     action,
		"observation": observation,
	}, nil
}

func (rt *AgentRuntime) selectAction(
	ctx context.Context,
	query string,
	manifest *bridge.CapabilityManifest,
	grant *bridge.ExecutionGrant,
) (*bridge.Action, error) {
	// 检查是否有匹配的Skill
	for name, skill := range rt.skills {
		if skill.ShouldApply(ctx, query, rt.state) {
			log.Printf("Applying skill: %s\n", name)
			return skill.Execute(ctx, query, rt.state)
		}
	}

	// 默认动作:创建节点
	return &bridge.Action{
		Type: bridge.OpCreateNode,
		Payload: map[string]interface{}{
			"kind":   "image",
			"prompt": query,
		},
		ToolCalls: []bridge.ToolCall{
			{
				Family:    bridge.ToolGeneration,
				Name:      "generate_image",
				Params:    map[string]interface{}{"prompt": query},
				RequestID: fmt.Sprintf("req_%d", rt.turnCount),
			},
		},
	}, nil
}

func (rt *AgentRuntime) executeAction(ctx context.Context, action *bridge.Action) (*bridge.ToolObservation, error) {
	start := time.Now()

	for _, tc := range action.ToolCalls {
		handler, ok := rt.toolHandlers[tc.Family]
		if !ok {
			return nil, fmt.Errorf("no handler for tool family: %s", tc.Family)
		}
		obs, err := handler(ctx, tc)
		if err != nil {
			return nil, err
		}
		_ = obs
	}

	return &bridge.ToolObservation{
		ToolCallID: action.ToolCalls[0].RequestID,
		Success:    true,
		DurationMs: time.Since(start).Milliseconds(),
	}, nil
}

func (rt *AgentRuntime) collectFeedback(obs *bridge.ToolObservation) *bridge.FeedbackSignal {
	if !obs.Success {
		return &bridge.FeedbackSignal{
			Source:   "evaluator",
			Verdict:  "reject",
			Comments: fmt.Sprintf("Tool execution failed: %s", obs.Error),
		}
	}
	return nil
}

func (rt *AgentRuntime) decideRepair(feedback *bridge.FeedbackSignal, state *canvas.CanvasState) *bridge.RepairDecision {
	if feedback == nil {
		return &bridge.RepairDecision{Action: "continue"}
	}
	if feedback.Verdict == "reject" {
		return &bridge.RepairDecision{
			Action:   "repair",
			Strategy: "regenerate_with_feedback",
			Params:   map[string]interface{}{"feedback": feedback.Comments},
		}
	}
	return &bridge.RepairDecision{Action: "continue"}
}

func (rt *AgentRuntime) applyRepair(ctx context.Context, repair *bridge.RepairDecision, state *canvas.CanvasState) {
	for _, nodeID := range repair.TargetNodes {
		node, err := state.GetNode(nodeID)
		if err == nil && node != nil {
			state.UpdateNode(nodeID, func(n *canvas.Node) {
				n.Status = canvas.StatusPlanned
			})
			log.Printf("Repair scheduled for node %s: %s\n", nodeID, repair.Strategy)
		}
	}
}

三、反馈驱动的局部修复机制

3.1 修复流程

JarvisHub的反馈驱动修复机制是其长程创作能力的关键。其核心思想是:在长程创作中,失败是常态,局部修复比全局重做更高效

反馈驱动修复流程:

输入: 画布状态 C_t, 动作 a_t, 观察 o_t
输出: 修复后的画布状态 C_{t+1}

1. 执行动作 a_t,获得观察 o_t
2. 收集反馈信号 f_t:
   - 用户反馈: 选择/拒绝候选、修改Prompt、标记缺陷
   - 模型评估器: 一致性检查、视觉质量、任务约束
   - 子Agent批评: 独立评估
3. 确定修复决策 r_t:
   ├─ continue: 继续下一个步骤
   ├─ repair: 定位失败节点 → 局部修复
   │    ├─ 标记失败节点为 planned
   │    ├─ 保留其上游依赖
   │    ├─ 使用反馈信号作为修正上下文
   │    └─ 重新生成
   ├─ clarify: 请求用户澄清
   └─ stop: 证据不足,停止
4. 应用状态转换 C_{t+1} = F(C_t, a_t, o_t, f_t, r_t)

3.2 局部修复 vs 全局重做

假设一个视频创作项目包含10个镜头,第5个镜头生成失败:

  • 全局重做(如Chatbot Agent):重新生成整个Prompt,丢失所有已接受的选择和上下文
  • 局部修复(JarvisHub):仅修复第5个镜头,保留其他9个镜头的状态和依赖关系
# repair/local_repair.py — 局部修复算法实现

from typing import List, Optional, Set
from dataclasses import dataclass

from canvas_state import (
    CanvasState, Node, NodeKind, RuntimeStatus, EdgeKind
)


@dataclass
class RepairPlan:
    """修复计划"""
    failed_node_id: str
    affected_downstream: List[str]  # 需要级联修复的节点
    preserved_upstream: List[str]   # 保留的上游依赖
    repair_strategy: str
    feedback_context: str


class LocalRepairEngine:
    """局部修复引擎"""

    def __init__(self, state: CanvasState):
        self.state = state

    def analyze_failure(self, feedback: str, failed_node_id: str) -> RepairPlan:
        """
        分析失败并制定修复计划

        核心算法:
        1. 定位失败节点及其下游依赖
        2. 保留上游依赖(不重新生成)
        3. 只标记受影响的下游节点需要修复
        """
        node = self.state.get_node(failed_node_id)
        if not node:
            raise ValueError(f"Node {failed_node_id} not found")

        # 找到所有需要级联修复的下游节点
        affected = self._find_affected_downstream(failed_node_id)

        # 找到可以保留的上游依赖
        preserved = self._find_preserved_upstream(failed_node_id)

        # 确定修复策略
        strategy = self._determine_strategy(node, feedback)

        return RepairPlan(
            failed_node_id=failed_node_id,
            affected_downstream=affected,
            preserved_upstream=preserved,
            repair_strategy=strategy,
            feedback_context=feedback,
        )

    def _find_affected_downstream(self, node_id: str) -> List[str]:
        """
        BFS遍历下游依赖图,找到所有受影响的节点
        """
        affected: Set[str] = set()
        queue = [node_id]

        while queue:
            current = queue.pop(0)
            dependents = self.state.get_dependents(current)
            for dep in dependents:
                if dep.id not in affected:
                    affected.add(dep.id)
                    queue.append(dep.id)

        return list(affected)

    def _find_preserved_upstream(self, node_id: str) -> List[str]:
        """
        找到可以保留的上游依赖(这些节点不受影响)
        """
        preserved: Set[str] = set()
        queue = [node_id]

        while queue:
            current = queue.pop(0)
            upstream = self.state.get_upstream(current)
            for up in upstream:
                if up.id not in preserved and up.status == RuntimeStatus.COMPLETED:
                    preserved.add(up.id)
                    queue.append(up.id)

        return list(preserved)

    def _determine_strategy(self, node: Node, feedback: str) -> str:
        """根据节点类型和反馈确定修复策略"""
        strategy_map = {
            NodeKind.IMAGE: "regenerate_with_feedback",
            NodeKind.VIDEO: "regenerate_segment",
            NodeKind.STORYBOARD: "revise_layout",
            NodeKind.TEXT: "revise_with_feedback",
            NodeKind.WEBPAGE: "patch_element",
            NodeKind.SLIDE: "revise_slide",
        }
        return strategy_map.get(node.kind, "regenerate_with_feedback")

    def apply_repair(self, plan: RepairPlan) -> int:
        """
        应用修复计划

        返回: 修复的节点数
        """
        repair_count = 0

        # 标记失败节点为待修复
        self.state.update_node(
            plan.failed_node_id,
            status=RuntimeStatus.PLANNED,
            metadata={
                "repair_strategy": plan.repair_strategy,
                "feedback_context": plan.feedback_context,
                "repair_attempts": 1,
            }
        )
        repair_count += 1

        # 标记受影响的下游节点为待修复
        for node_id in plan.affected_downstream:
            node = self.state.get_node(node_id)
            if node and node.status in (RuntimeStatus.COMPLETED, RuntimeStatus.ACCEPTED):
                # 标记为待修复,但保留输出历史
                self.state.update_node(
                    node_id,
                    status=RuntimeStatus.PLANNED,
                )
                repair_count += 1

        # 确认上游依赖保持不变
        logger.info(
            f"Repair plan: {repair_count} nodes to repair, "
            f"{len(plan.preserved_upstream)} upstream nodes preserved"
        )

        return repair_count

四、与现有方案的深度对比

4.1 Prompt-to-Output工具

代表:Midjourney、DALL·E、Stable Diffusion、FLUX

维度Prompt-to-OutputJarvisHub
状态持久化无,每次生成独立画布持久化所有中间状态
版本管理需手动管理自动版本边记录
局部编辑需Inpainting/Outpainting通过画布节点精确寻址
失败恢复重新生成局部修复
多模态编排有类型资产图

4.2 Chatbot Agent

代表:Claude Design、GPT-4 with Tools、各类Agent框架

维度Chatbot AgentJarvisHub
上下文存储线性对话历史结构化画布图
状态可见性隐藏于LLM内部画布完全可见
局部编辑目标模糊文本描述精确节点寻址
依赖跟踪隐含显式边
并行探索困难Subagents

4.3 节点式工作流

代表:ComfyUI、PromptChainer、StoryNodes

维度节点式工作流JarvisHub
管线构建人工预设Agent动态构建
状态演化固定拓扑动态图
Agent介入无,手动执行全自动
反馈回路反馈驱动修复
适用场景固定流程长程开放创作

五、三个验证案例的技术分析

5.1 叙事媒体生成

过程:故事Brief → 角色设定 → 场景设计 → 故事板 → 分镜 → 图像序列 → 视频片段 → Animatic

JarvisHub的独特价值

  • 角色一致性:通过参考关系边,确保跨镜头角色外观一致
  • 版本谱系:每个候选镜头保留版本历史,方便回溯
  • 跨镜头连续性:场景节点作为共享上下文,维持叙事连贯

5.2 交互式网页开发

过程:设计Brief → 视觉参考 → 布局草稿 → 前端代码 → 渲染预览 → 迭代修订

JarvisHub的独特价值

  • 响应式验证:预览节点对应不同断点,通过边关联
  • 组件复用:UI组件节点可跨页面引用
  • 视觉一致性:设计参考节点作为上游依赖,约束所有生成

5.3 演示文稿生成

过程:主题 → 内容规划 → 图示生成 → 幻灯片布局 → 格式检查 → 导出

JarvisHub的独特价值

  • 跨页一致性:幻灯片节点共享主题和样式引用
  • 内容组织:分组关系边组织章节结构
  • 渐进式构建:先生成内容大纲,再填充细节

六、未来展望与研究意义

6.1 开放性研究基础设施

JarvisHub的开放设计为未来研究提供了三个关键能力:

  1. 项目状态基准(Project-State Benchmarks):每个任务指定初始画布、参考素材、可用工具、约束条件、反馈事件和预期检查点,而不是仅提供Prompt和期望答案。
  2. 过程级评估:结合最终资产质量和过程指标——上下文保留、工具使用适切性、依赖正确性、反馈遵从度、修复成功率。
  3. 数据飞轮:每次运行产生结构化轨迹数据,可用于训练未来Agent的规划、工具选择、多模态状态跟踪、局部修复和反馈引导修订能力。

6.2 局限与边界

  • 当前论文报告的是定性案例,而非完整Benchmark
  • 最终资产质量仍取决于外部模型和工具
  • Protocol Bridge不能保证创作决策的语义正确性
  • 轨迹数据使用前需进行质量过滤、用户授权、匿名化和版权审查

结语

JarvisHub的发布标志着AI创作Agent从"黑盒渲染"到"可回放画布"的重要转变。它将画布从视觉界面提升为Agent和人类共享的项目状态空间,通过三层架构——Canvas State、Protocol Bridge、Agent Runtime——实现了长程多模态创作中状态管理的系统性解决方案。

对于AI Agent研究者而言,JarvisHub提供了一个难得的开放实验平台。对于创作者而言,它意味着一个可检查、可干预、可恢复的创作伙伴。正如项目主页所言:“Stop shipping black-box renders. Ship a canvas you can replay.”


本文基于JarvisX团队论文《JarvisHub: An Open Harness for Canvas-Native Multimodal Creative Agents》(arXiv:2607.23588)和开源项目编写。