Skip to content

Recipe Development Guide ​

This document is a complete development reference for the Recipe subsystem. A Recipe is Familiars' "single-skill automation graph": a DAG runtime where LLM calls and tool executions are nodes, the variable pool is the state, and conditional edges are branches.


1. Concept Model ​

1.1 What is a Recipe ​

Recipe maps one "turn of conversation" of an agent to a state graph:

  • Entry: start node (the single global entry anchor)
  • Exit: done node (the single global exit), must bind output / finish_reason
  • In between: compute / llm_call / tool_call / rag_query / emit_event / for_each nodes

Each node reads its inputs from the global variable pool and writes outputs back to it; an edge's condition= is a Python boolean expression; multiple outgoing edges can branch concurrently (the host round-robins across active branches).

1.2 Relationship to the Legacy Pipeline ​

The legacy manifest.chains + hooks linear chain is a special-case graph where every edge condition is "True". src/recipes/graph/translator.py automatically compiles a legacy manifest into graph.json (cached as _auto_graph.json); once the user saves in the editor it is written as the official graph.json, and from then on the manifest's chains/hooks fields are no longer read.


2. Directory and Files ​

2.1 Source Tree ​

src/recipes/
├── __init__.py
├── manifest.py             # RecipeManifest 模型(输入/输出/工具/知识库/客户端/RAG/事件类型/渲染器)
├── store.py                # RecipeStore — 磁盘 layout + CRUD
├── context.py              # RecipeContext — bind 时上下文
├── runtime.py              # Graph 运行期入口
├── catalog.py              # 工具目录构建(_build_tools_catalog)
├── chain_catalog.py        # 旧链式目录
├── approvals.py            # 能力审批
├── audit.py                # 审计
├── eval.py                 # 评估
├── filters.py              # 过滤器
├── folder_protocol.py      # 文件夹协议
├── heartbeat.py            # 心跳 cron
├── marketplace.py          # marketplace 索引
├── migrations.py           # 迁移
├── packaging_cli.py        # CLI 打包
├── reasoning.py            # 推理
├── renderer_compiler.py    # .vue 渲染器预编译
├── runtime_flags.py        # 运行时 flag
├── source_reader.py        # 源码读取
├── authoring/              # Copilot 辅助创作
│   ├── orchestrator.py
│   ├── prompts.py
│   └── state.py
├── graph/                  # ★ 状态图核心
│   ├── schema.py           # RecipeGraph / GraphNode / GraphEdge / GraphVariable / SubGraphTemplate / SubgraphIO
│   ├── validate.py         # validate_graph — 20+ 条静态校验
│   ├── runner.py           # 状态图执行器(_dispatch_chain_slot, _chain_node, _expand_for_each_in_scope)
│   └── translator.py       # manifest chains/hooks → RecipeGraph
└── sandbox/                # 沙箱执行
    ├── ast_guard.py        # AST 白名单(permanently denied / requestable)
    ├── host.py             # 沙箱宿主
    ├── snippet.py          # call_snippet — 把代码包成 def _node_<uuid>(**inputs): ...
    └── worker.py           # 沙箱 worker

2.2 Runtime Disk Layout ​

Each recipe lives under workspace/agent_recipes/<id>/:

workspace/agent_recipes/<id>/
├── manifest.json            # RecipeManifest
├── user_code/
│   ├── graph.json           # RecipeGraph(核心,前端编辑器的源真相)
│   ├── *.py                 # 自定义 compute / args_builder / result_parser 代码
│   └── tools/               # manifest.tools.custom_tools 的 .py 实现
│       └── <tool_name>.py   # 顶层函数名必须 == tool key
├── knowledge/               # manifest.knowledge.identity/agents_md/user_md 指向的 md 文件
│   ├── IDENTITY.md
│   ├── AGENTS.md            # ★ recipe SKILL 通过 execute_skill 进入 flow,必须存在
│   └── USER.md
├── external_kb/<kb_name>/   # manifest.knowledge.external_kbs 对应的文件目录
├── skills/                  # manifest.skills.custom_templates 的 markdown
├── renderers/               # manifest.event_types[].renderer 与 output_renderers[].source_file
│   └── *.vue
└── recipes/                 # 归档的 recipe(嵌套)

2.3 Built-in Recipes ​

Shipped with the repository, auto-registered by install_built_ins(), listed under the "System" group in the frontend, not editable/deletable:

IDPathDescription
hello_worldsrc/recipes/_builtin/hello_world/Minimal graph recipe — one compute node echoes the user input in reverse and then goes done
default_recipesrc/recipes/_builtin/default_recipe/Default recipe — a full state-graph mirror of the generate() main pipeline (sensitive → triage → RAG → planner → executor → synthesizer → verifier → replanner)
recipe_author_agentsrc/recipes/_builtin/recipe_author_agent/Recipe-authoring assistant agent

3. Schema Reference ​

3.1 RecipeGraph (graph.json Top Level) ​

python
class RecipeGraph(BaseModel):
    version: int = 1
    variables: list[GraphVariable]          # 全局变量池
    nodes: list[GraphNode]                   # 节点集合
    edges: list[GraphEdge]                   # 边缘集合
    subgraphs: list[SubGraphTemplate] = []   # for_each 引用的子图模板(legacy)
    entry: str = ""                          # legacy 单入口(迁移到 entries)
    entries: list[str] = []                  # 多入口(START 节点存在时被强制为 [start.id])
    exits: list[str] = []                    # 默认 = 所有 done 节点

3.2 GraphVariable ​

python
class GraphVariable(BaseModel):
    name: str                  # identifier-like(alnum + underscore)
    type: str = "any"          # str|int|float|bool|list|dict|any
                               # + 别名:string|number|integer|boolean|array|object → 规范化
    external: bool = False     # True = host 在 turn 开始注入(只读)
    desc: str = ""

Default external variables (injected automatically by the host each turn; the recipe need not declare them):

NameTypeDescription
querystrUser's raw input for this turn
historylistList of historical dialogue messages
attachmentslistUser's attachment list for this turn
agent_idintCurrent agent id
dialogue_idintCurrent dialogue id

3.3 GraphNode ​

python
class GraphNode(BaseModel):
    id: str                            # identifier-like
    kind: str = "compute"              # start|compute|llm_call|tool_call|rag_query|emit_event|for_each|done
    inputs: list[str] = []             # 从变量池读
    outputs: list[str] = []            # 写回变量池
    code: str = ""                     # compute 节点用(snippet:return {outputs})
    args_builder: str = ""             # side-effect 节点用(snippet,构造 action args)
    result_parser: str = ""            # side-effect 节点用(snippet,解析 observation)
    desc: str = ""
    mode: str = "custom"               # custom | native
    config: dict = {}                  # native 模式的结构化配置
    render_as: dict[str, str] = {}     # done 节点专属:metadata key → 渲染 hint(markdown|image|json|text|raw)
    position_x: float = 0.0            # UI 位置
    position_y: float = 0.0
    # for_each 内联 body(2026-08-07  redesign,优先于 config.body_template)
    body_nodes: Optional[list[GraphNode]] = None
    body_edges: Optional[list[GraphEdge]] = None
    body_entry: str = ""
    body_exit: str = ""
    body_inputs: Optional[list[SubgraphIO]] = None
    body_outputs: Optional[list[SubgraphIO]] = None

Node kind Semantics ​

KindMeaningRequired fields (custom mode)Required fields (native mode)
startGlobal entry anchorNone (pure anchor; the schema auto-clears all fields)Same
computePure local Python, no side effectscodeSame
llm_callHost calls the LLMargs_builder + result_parserconfig.system_prompt / user_prompt_template / output_format
tool_callHost calls a toolargs_builder (returns {tool_name, arguments}) + result_parserconfig.tool_name + config.argument_map
rag_queryHost queries the KBargs_builder + result_parserconfig.source + config.query_input
emit_eventHost pushes an event to the frontend (can block waiting for a user response)args_builderconfig.event_type + config.field_values
for_eachExpanded N times at runtime (dynamic loop)None (relies on config)Same
doneTerminates a branchargs_builder (returns {output, finish_reason})Same

3.4 GraphEdge ​

python
class GraphEdge(BaseModel):
    source: str                      # 起节点 id
    target: str                      # 终节点 id
    condition: str = "True"          # Python bool 表达式(AST 白名单求值)
    directed: bool = True            # False = 双向,任一端完成都触发另一端
    source_handle: str = ""          # UI: VueFlow handle id
    target_handle: str = ""
    join_id: str = ""                # AND-edge 分组(相同 join_id + 相同 target = 所有 source 都触发才跑 target)

3.5 Native Mode Config Schema ​

When mode="native", the runner calls host actions directly (ConversationSession / ToolDispatcher / Memorizer) instead of running a Python snippet.

llm_call native config ​

python
{
    "system_prompt": str | None,          # 内联系统提示
    "system_prompt_file": "" | "AGENTS.md" | "IDENTITY.md",  # 从 knowledge 文件加载
    "user_prompt_template": str,          # 支持 {{var}} 模板
    "output_format": {
        "type": "str" | "markdown" | "json" | "schema" | "schema_ref" | "schema_from_var",
        "schema": {...},                  # type=schema 时的 JSON Schema
        "ref": "plan_spec.json",          # type=schema_ref 时的文件名
        "schema_var": "my_schema",        # type=schema_from_var 时的变量名
        "on_parse_error": "soft" | "hard"
    },
    "output_var": str,                    # 把 parsed dict 包到一个变量名下(防止字段散到顶层)
    "output_map": {ret_field: var_name},  # 把 LLM 输出字段映射到变量名
    "tool_whitelist": [str],              # 本节点允许调用的工具
    "loop_mode": "single" | "tool_loop",  # tool_loop 时 LLM 多轮发 tool_call
    "max_turns": int,                     # tool_loop 上限
    "history_mode": "none" | "inherit" | "custom",
    "client_id": str                      # 指定 client(覆盖 manifest default)
}

tool_call native config ​

python
{
    "tool_name": str,                    # 必须存在且可通过 ToolDispatcher 调用
    "argument_map": {param: var_name | "literal_value"}  # 字面量用非 identifier 字符串
}

rag_query native config ​

python
{
    "source": "memorizer" | "external_kb" | "skills_registry" | "data_registry" | "dialog_history",
    "query_input": str,                  # 必须 in node.inputs
    "kb_names": [str],                   # source=external_kb 时必须,且要在 manifest.knowledge.external_kbs 里
    "top_k": int,
    "result_map": {field: var_name}
}

emit_event native config ​

python
{
    "event_type": str,                   # 内建:__log__ | __output__;或 manifest.event_types 里声明的
    "field_values": {field_name: value | "=expr"},  # =expr 在运行时对变量池求值
    "mode": "custom" | "native"          # custom 时会忽略 native config
}

3.6 for_each Node Config ​

python
{
    "iterable_var": str,                 # 必须 in graph.variables(要迭代的变量)
    "iter_var": str,                     # 每次迭代绑定的当前项名字(不能 == iterable_var)
    "body_template": str,                # 引用 SubGraphTemplate(与 inline body 二选一)
    # --- 或者 inline body(2026-08-07 设计)---
    # node.body_nodes / body_edges / body_entry / body_exit / body_inputs / body_outputs
    "mode": "parallel" | "serial",       # 默认 parallel
    "output_var": str,                   # 迭代结果收集到哪个变量(必须 in node.outputs)
    "reduce_strategy": "list" | "concat" | "merge_dict" | "last",
    "result_var": str,                   # 每次迭代里要收集的变量(merge_dict 时必设)
    "input_bindings": {sub_input: parent_var},
    "output_bindings": {sub_output: parent_var}
}

3.7 RecipeManifest (manifest.json Top Level) ​

Key fields (the pydantic model lives in src/recipes/manifest.py):

python
class RecipeManifest(BaseModel):
    id: str                              # 全局唯一,== 目录名
    name: str
    description: str = ""
    version: str = "1.0.0"
    author: str = ""
    tags: list[str] = []
    # 输入输出契约
    input_vars: list[RecipeVar]          # RecipeVar: name, type, description, required
    output_vars: list[RecipeVar]
    # 工具
    tools: RecipeTools = {
        "whitelist": None | [str],       # None = 全部;[str] = 只这些
        "per_tool_overrides": {tool: file.py},
        "custom_tools": {tool: CustomToolDef},
        "categories": [ToolCategoryDef],
        "tool_categories": {tool: category}
    }
    # RAG
    rag: RecipeRAG = {
        "mode": "builtin" | "custom",
        "alpha": float, "beta": float, "gamma": float,
        "top_k": int, "sliding_window_size": int,
        "user_code": str,
        "hybrid_sparse_weight": float, "rerank_disabled": bool,
        "graph_expand_top": int, "graph_expand_hops": int
    }
    # 技能
    skills: RecipeSkills = {"enabled": [], "custom_templates": []}
    # 知识
    knowledge: RecipeKnowledge = {
        "identity": str, "agents_md": str, "user_md": str,
        "external_kbs": [ExternalKB],
        "kb_categories": [KbCategoryDef]
    }
    # 生成管线
    generate: RecipeGenerate = {
        "image": RecipeGenerateImage,
        "report": RecipeGenerateReport,
        "digital_human": RecipeGenerateDigitalHuman
    }
    # 客户端覆盖
    clients: RecipeClients = {
        "existing": {client_id: ExistingClientOverride},
        "custom": [CustomClient]
    }
    # 自定义事件类型
    event_types: list[RecipeEventType]
    # 输出渲染器
    output_renderers: list[RecipeOutputRenderer]
    # 能力
    capabilities: {"extra_modules": [str]}
    # 执行
    execution: {"parallel_mode": "topo" | "..."}
    # 历史管理
    history: RecipeHistory = {"mode": "builtin" | "custom", "filename": str}
    # 长期记忆
    memorizer: RecipeMemorizer = {"auto_write": bool}

4. REST API ​

4.1 Recipe CRUD (/api/agent_recipes) ​

MethodPathDescription
GET/api/agent_recipesList all recipes
GET/api/agent_recipes/{id}Fetch one
POST/api/agent_recipesCreate
PUT/api/agent_recipes/{id}Update manifest
DELETE/api/agent_recipes/{id}Delete
POST/api/agent_recipes/{id}/duplicateDuplicate
POST/api/agent_recipes/{id}/set_defaultSet as default for an agent
POST/api/agent_recipes/{id}/create_agentCreate an agent bound to this recipe

4.2 Docs & Utility Files (/api/agent_recipes/{id}/...) ​

MethodPathDescription
GET/{id}/docsList files in the knowledge directory
GET/{id}/docs/{name}Read a file
PUT/{id}/docs/{name}Write a file
DELETE/{id}/docs/{name}Delete
GET/{id}/utilsList tool .py files under user_code/
GET/{id}/utils/{name}Read tool source
PUT/{id}/utils/{name}Write tool source
POST/{id}/utils/uploadMultipart upload a tool .py
GET/{id}/renderersList renderers
GET/{id}/renderers/{name}Read a renderer's .vue source

4.3 Import / Export ​

MethodPathDescription
GET/{id}/export_zipExport as .zip
POST/import_zipImport from .zip
POST/preview_zipPreview .zip contents
POST/import_folderImport from a directory

4.4 Development & Publishing (/api/recipe_dev) ​

MethodPathDescription
POST/api/recipe_dev/compileCompile validation (structural)
POST/api/recipe_dev/dryrunDry run (no real model; uses a stub asker)
POST/api/recipe_dev/e2e_testEnd-to-end test
POST/api/recipe_dev/publish_relayPublish to the relay marketplace
GET/api/recipe_dev/review_statusView review status
GET/api/recipe_dev/catalogTool catalog
GET/api/recipe_dev/_meta/protocolProtocol metadata

4.5 State Graph Read/Write ​

Saving actually goes through PUT /api/agent_recipes/{id} with a merged manifest + graph.json payload; the frontend editor POSTs the edited graph directly.


5. Validator (validate_graph) ​

src/recipes/graph/validate.py:validate_graph(graph, manifest, agent_id) returns a list of violations: {node_id?, field?, code, message, severity}. A severity of "error" blocks saving; "warning" only shows a hint.

5.1 Validation Checklist ​

CodeSeverityMeaning
NO_DONEerrorNo done node
MULTIPLE_DONEerrorMultiple done nodes
NO_STARTwarningNo start node (legacy mode still works)
MULTIPLE_STARTerrorMultiple start nodes
START_HAS_INCOMING_EDGEerrorThe start node has an incoming edge
START_NOT_SOLE_ENTRYerrorentries ≠ [start.id]
START_IS_EXITerrorstart is also an exit
JOIN_UNDIRECTEDerrorA join_id member is an undirected edge
JOIN_MULTIPLE_TARGETSerrorThe same join_id points to multiple targets
JOIN_DANGLINGerrorA join_id member references a nonexistent node
JOIN_SINGLE_MEMBERwarningA join_id group has only one edge
ORPHAN_SUBGRAPHwarningA SubGraphTemplate is not referenced by any for_each
DUP_SUBGRAPH_IDerrorDuplicate subgraph id
BODY_ENTRY_UNREACHABLEwarningThe subgraph's body_entry has no incoming edge
FE_NO_ITER_VARerrorfor_each is missing iter_var
FE_NO_ITERABLEerrorfor_each is missing iterable_var
FE_ITERABLE_NOT_DECLAREDerroriterable_var is not in variables
FE_ITER_VAR_COLLISIONerroriter_var == iterable_var
FE_NO_BODYerrorNeither body_template nor an inline body
FE_BODY_NOT_FOUNDerrorbody_template is not in subgraphs
FE_BAD_MODEerrormode is not parallel/serial
FE_BAD_REDUCEerrorInvalid reduce_strategy
FE_OUTPUT_NOT_DECLAREDerroroutput_var is not in variables
FE_NO_OUTPUTSwarningnode.outputs is empty
FE_BODY_DUP_IDerrorDuplicate id within body_nodes
FE_NO_BODY_ENTRY / FE_NO_BODY_EXITerrorMissing body_entry / body_exit
FE_BODY_ENTRY_MISSING / FE_BODY_EXIT_MISSINGerrorbody_entry/body_exit not in body_nodes
FE_BODY_EDGE_DANGLINGerrorbody_edges references a nonexistent node
FE_BODY_DUP_IOerrorDuplicate names in body_inputs / body_outputs
FE_INLINE_OVERRIDES_TEMPLATEwarningBoth an inline body and body_template are set
FE_BODY_UNDECLARED_VARerrorA body node references an undeclared variable
FE_BODY_INPUT_MISSING_IN_PARENTwarningA body_inputs name has no corresponding variable in the parent graph
FE_MERGE_DICT_NEEDS_RESULT_VARwarningmerge_dict has no result_var set
MISSING_OUTPUT_KEYSerrorThe compute return dict is missing declared output keys
NO_RETURNwarningcompute has no return
NON_LITERAL_RETURNwarningThe compute return is not a literal dict
SNIPPET_UNDEFINED_INPUTerrorThe snippet references a variable not in node.inputs
NO_TOOL_NAMEerrorA native tool_call has no tool_name
TOOL_NOT_CALLABLEerrorTool is not in the catalog
TOOL_NOT_IN_WHITELISTerrorTool is not in the manifest tools.whitelist
ARG_VAR_NOT_INPUTerrorAn argument_map value is not in node.inputs
BAD_RAG_SOURCEerrorInvalid rag_query source
NO_QUERY_INPUTerrorrag_query has no query_input
KB_NOT_IN_MANIFESTerrorkb_names not in manifest external_kbs
NO_EVENT_TYPEerroremit_event has no event_type
UNKNOWN_EVENT_TYPEerrorevent_type is not in manifest event_types
NO_RESPONSE_TARGETerrorexpects_response but no outputs / response_var
OUTPUT_VAR_NOT_DECLAREDerrorAn output_map value is not in node.outputs
INPUT_VAR_UNRESOLVEDerrorA variable in node.inputs is neither injected by the host nor written by any node
START_INPUT_UNUSEDerrorquery is not referenced by any downstream node
OUTPUTS_NOT_BINDABLEerrorA native llm_call has outputs but no output_format/output_map/output_var
BAD_SYSTEM_PROMPT_FILEerrorsystem_prompt_file is not ''/AGENTS.md/IDENTITY.md
BAD_OUTPUT_FORMATerrorInvalid output_format.type
BAD_CLIENT_IDerrorclient_id is not a string / not in the manifest
TOOL_NOT_IN_MANIFESTerrortool_whitelist is not in the manifest
BAD_HISTORY_MODEerrorInvalid history_mode
BAD_MAX_TURNSerrormax_turns is not a positive integer

6. Sandbox & AST Allowlist ​

6.1 Permanently Denied ​

ctypes / pickle / importlib / inspect / code / codeop / pdb / trace / tracemalloc / sys / signal / multiprocessing / threading / asyncio / socket / select / asyncore / asynchat / ssl / mmap / platform / __import__ / eval / exec / compile / open / globals / locals / vars / getattr / setattr / delattr / breakpoint / input / exit / quit

6.2 Requestable Modules ​

Declared under capabilities.extra_modules, then gated by approval: os / subprocess / requests / aiohttp / httpx / sqlite3 / pathlib / tempfile / hashlib / hmac / base64 / binascii / uuid / secrets / shutil / glob / fnmatch / textwrap / io / copy / pprint / urllib / xml / csv / string / bisect / heapq / operator / itertools / random / fractions / warnings / contextlib / abc / types / argparse

Granting src.x unlocks the entire src/ subtree — use with care.

6.3 Capability Approval ​

Each (recipe_id, agent_id) pair has an independent approval hash. install_built_ins() auto-approves built-in recipes. The bind / setDefault responses carry caps_declared + needs_approval, and the frontend shows a CapabilityApprovalPanel.


7. Runtime Behavior ​

7.1 Node Execution ​

  • compute: wraps code into def _node_<uuid>(**inputs): ... and runs it inside the sandbox; the returned dict is written back to the variable pool.
  • llm_call custom: args_builder builds the messages, the host calls ConversationSession, and result_parser parses the observation.
  • llm_call native: the host calls ConversationSession directly from config (with a tool_whitelist it runs a tool loop).
  • tool_call: args_builder builds {tool_name, arguments}, and the host calls ToolDispatcher.
  • rag_query: the host runs RAG recall and result_parser parses it.
  • emit_event: the host yields an event to the frontend; if the event_type has expects_response=True, it blocks waiting for the user's response, which is written back to response_var or node.outputs.
  • for_each: expanded at runtime; each iteration creates a new variable scope with a snapshot of the parent pool plus the iter_var binding.

7.2 Variable Pool Semantics ​

  • Global pool: shared by all nodes. Parallel branches writing the same variable → last-write-wins + warning.
  • External variables: injected by the host at the start of a turn (query / history / attachments / agent_id / dialogue_id).
  • for_each iteration scope: automatically includes a snapshot of the parent pool + iter_var + <iter_var>_idx. Inline bodies are a strict allow-list: a body node can only read what body_inputs declares, iter_var, and what other body nodes write.

7.3 Recipe-SKILL Collaboration ​

A Recipe invokes SKILL.md through the execute_skill pseudo-tool; the SKILL takes effect when the recipe's knowledge/AGENTS.md exists. Agents inside a Flow see these tools via filtered_schemas, not tools.json.


8. Custom Renderers ​

8.1 Output Renderers (RecipeOutputRenderer) ​

json
{
  "output_renderers": [
    {"name": "couplet", "output_var": "final_output", "source_file": "couplet.vue"}
  ]
}

.vue files live under renderers/; on save the backend's renderer_compiler pre-compiles them to ESM JS, which the frontend mounts when rendering chat output. The component receives :value="output_fields[output_var]".

8.2 Custom Event Types (RecipeEventType) ​

json
{
  "event_types": [
    {
      "name": "approval_card",
      "renderer": "approval_card.vue",
      "fields": [
        {"name": "title", "type": "string"},
        {"name": "options", "type": "array", "items": {"name": "opt", "type": "string"}},
        {"name": "audio_url", "type": "audio"}
      ],
      "expects_response": true,
      "response_var": "user_choice"
    }
  ]
}

The emit_event node uses event_type: "approval_card" + field_values: {...}. The runner resolves =expr, mounts the .vue, and waits for the response to be written back to response_var.

Supported field types: string / int / float / bool / array / object / audio / video / attachment / attachments. Media types produce a {kind, path} attachment dict that the frontend plays via the app-file:// protocol.


9. Common Pitfalls ​

PitfallSymptomFix
tool_loop node idlingThe LLM never emits a tool_callIn the system_prompt explicitly say "emit ≥1 tool_call/turn"; use a markdown output_format instead of a schema
output_var not setSchema fields spread to the top level and downstream .field access failsAdd output_var in config to wrap them
.get() on a conditional edgeSilently evaluates to FalseUse x['key'] subscript syntax
Native llm_call has outputs but no output_formatDownstream done gets an empty stringAdd output_format.type: "schema" or output_var
Changes to the translator don't take effectStale graph behaviorRestart the bridge or re-run install_built_ins
copilot uses user_input instead of queryquery is never consumedSTART_INPUT_UNUSED will error out; always use query
merge_dict without result_varLater iterations overwrite earlier onesSet result_var to the output variable of each iteration
save_image_to_recipe times outHTTP 600s is not enoughSet timeout_s: 1800 on the custom tool

10. Development Workflow (End-to-End) ​

  1. Create a recipe in the Creative Workshop → drag nodes and connect edges in the editor;
  2. Edit node parameters (LLM prompt / tool / expression / renderer);
  3. Compile: POST /api/recipe_dev/compile (structural validation; validate_graph outputs violations);
  4. Dry run: POST /api/recipe_dev/dryrun (no real-model dependencies; uses a stub asker);
  5. e2e test: POST /api/recipe_dev/e2e_test (real LLM + tools);
  6. Publish: POST /api/recipe_dev/publish_relay (enters the review queue).

Reference Source Code ​

  • Schema: src/recipes/graph/schema.py
  • Validation: src/recipes/graph/validate.py
  • Runtime: src/recipes/graph/runner.py
  • Manifest: src/recipes/manifest.py
  • Tool catalog: src/recipes/catalog.py
  • REST: src/bridge/routes/agent_recipes.py, src/bridge/routes/recipe_dev.py

Familiars · 多 Agent AI 桌面应用