Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
845bbb7
feat(manifest): literal inputs refuse templates at validate, compile …
pawellisowski Oct 12, 2026
edbd51b
feat(blender): exec dispatch and per-frame progress on the builtin tr…
pawellisowski Oct 12, 2026
1e5ab1e
feat(blender): exec command runs author-time bpy code with live frame…
pawellisowski Oct 12, 2026
70b7483
fix(blender): an abandoned or wedged render can no longer outlive its…
pawellisowski Oct 12, 2026
3c9dd1f
fix(blender): runs leave the stored package untouched; exec receipts …
pawellisowski Oct 12, 2026
64802f8
test(blender): the wedged stand-in exits instead of racing past a tre…
pawellisowski Oct 12, 2026
12f3a8e
fix(progress): a pump cancelled mid-send loses and repeats no record …
pawellisowski Oct 12, 2026
9790512
fix(blender): the timeout kills the whole process tree through the sh…
pawellisowski Oct 12, 2026
8b7d8a6
fix(blender): prove an output's authorship by snapshot, not a timesta…
pawellisowski Oct 12, 2026
f561b4c
fix(blender): each render lands its own file through a private scratc…
pawellisowski Oct 12, 2026
02f2eee
fix(blender): a render lands only its own format's file, and only if …
pawellisowski Oct 12, 2026
d8321b3
fix(app): a whole-node template cannot feed a literal input (#692 rev…
pawellisowski Oct 12, 2026
67d73e2
fix(blender): Ctrl-C kills Blender's process tree instead of orphanin…
pawellisowski Oct 12, 2026
b7cabf0
fix(blender): Ctrl-C still ends a one-shot run after a Blender call f…
pawellisowski Oct 12, 2026
207d079
Revert "fix(blender): Ctrl-C still ends a one-shot run after a Blende…
pawellisowski Oct 12, 2026
956cd5b
Revert "fix(blender): Ctrl-C kills Blender's process tree instead of …
pawellisowski Oct 12, 2026
2919552
fix(blender): Ctrl-C reaches Blender directly; no own process group (…
pawellisowski Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions 10-core/app-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,20 @@ One class of command is different: those that run **caller-supplied logic** whos

The author taking responsibility for an `exec` script's mode is the only authority that exists — no manifest can inspect an arbitrary script. This resolves issue #165: a read-only host-`exec` app is no longer mislabeled write-mode or forced to declare a bogus `safety:` block.

Not every `exec` is overridable. `blender`'s `exec` (#692) is fixed `mode: write` with no override, because a read-mode node runs for real under `--dry-run` and a preview must never render an animation.

#### Literal inputs (`literal: true`)

An input a command runs as a program — an `exec` command's `code` — must be pinned by the approval, and the `.lock` hashes the app source, not what a template later resolves to. A `code: '{{ inputs.code }}'` would run Python supplied after a person approved the lock. So an agent manifest may declare an input **`literal: true`**:

```yaml
inputs:
code: { type: string, required: true, literal: true }
args: { type: object } # data: still templatable
```

A node that gives a literal input any template syntax (`{{ }}`, `{% %}`, `{# #}`) is refused with **`E_APP_INPUT_MUST_BE_LITERAL`**, naming the node and the input, by `aware app validate`, by the lock compiler, and by the runtime. The runtime check reads the node's merged, **un-rendered** value before any template is rendered, so a lock produced by an older compiler cannot smuggle one through either. Pass run-time data through a templatable input (`args`) instead. Today only `blender`'s `exec.code` is literal; the other host `exec` commands adopting it is tracked separately (#693).

### The block

```yaml
Expand Down
84 changes: 83 additions & 1 deletion 20-agents/aeco/visualization/blender/manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
agent: blender
version: 0.1.0
version: 0.2.0
display-name: Blender
description: |
The first visualization agent in the substrate that renders with NO human in
Expand All @@ -12,6 +12,11 @@ description: |
renderers in the box (EEVEE for drafts and turntables, Cycles for path-traced
hero shots).

Beyond the fixed recipes, `exec` runs author-time `bpy` code -- a construction
sequence, a fly-through, a sun study -- written once when the workflow is
authored, pinned in the approved lock, and run unchanged on every run. Long
renders report per-frame progress while they work.

Materials are assigned from what the IFC already states: product class plus
associated material grade, with clay as a never-fail default. That is what
makes the chain autonomous — nobody is there to click a material.
Expand Down Expand Up @@ -44,6 +49,10 @@ requires:
filesystem:
- read: '*.ifc,*.blend'
- write: '*.blend,*.png,*.mp4'
# `exec` runs author code that writes its renders into `output-dir`. Like
# tekla/rhino exec, Python writes are not sandboxed, so this is declared as
# broadly as it is -- the contract a person approves with the lock.
- write: any # `exec` only

# Runtime built-in: the transport runs in-process in the `aware` runtime, spawning
# `blender -b -P <script>.py -- @<inputs>.json` and recovering the framed result
Expand Down Expand Up @@ -334,7 +343,80 @@ commands:
excluded: object # ifc-path branch only
excluded-count: number # ifc-path branch only

exec:
lifecycle: single
category: curated
# Write, and NOT mode-overridable: this runs arbitrary Python, and a node
# allowed to declare `mode: read` would be executed for real under
# `--dry-run`. A preview of an animation must never render one.
mode: write
description: |
Run author-time `bpy` code headlessly -- the way to make anything the
fixed recipes do not: a construction sequence, a camera fly-through, a
sun study, an exploded view, a highlight of changed elements.

The code is written once, when the workflow is authored, and lives in the
workflow file itself, so the approved lock pins exactly what runs; it is
never supplied at run time (a templated `code` is refused). Run-time data
travels in `args` instead.

Before the code runs, the scene is prepared: an `ifc-path` is imported
(with IFC class, material, storey, GUID and name on every object) and
given the `preset` look; a `blend-path` is opened as-is; with neither, the
code starts from an empty scene. The code defines `run(ctx)` and returns
anything JSON can carry. `ctx` offers the model's elements, camera
framing, looks, lighting, and `render_still` / `render_animation`, which
report per-frame progress while they render and record each file they
write in a verified receipt. See exec-bpy.md.
inputs:
code:
type: string
required: true
literal: true
description: |
Python source defining `run(ctx)`. Must be written out in the
workflow -- template syntax here is refused at validate, compile and
run, because a template would run code supplied after approval. Put
data in `args`.
args:
type: object
required: false
description: |
Free-form, templatable data the code reads as `ctx.args` -- storey
order, element GUIDs, frame counts. Data, never code.
ifc-path:
type: string
required: false
description: IFC to import and stage before the code runs; mutually exclusive with `blend-path`.
blend-path:
type: string
required: false
description: Staged .blend to open before the code runs; mutually exclusive with `ifc-path`.
preset:
type: enum
values: [clay, realistic, section-style]
default: realistic
description: Look applied when staging from `ifc-path`.
output-dir:
type: string
required: true
description: |
Where the code writes its renders (`ctx.output_dir`). Only files
inside it can be recorded as verified outputs.
timeout-seconds:
type: number
description: |
Process budget, default 1800. Set it from the render estimate --
frames times seconds per frame -- with headroom.
outputs:
type: single
schema:
result: any # run(ctx)'s JSON-serialisable return, at most 256 KiB
outputs: array # [{path, kind: video|image|blend|file, size-bytes, frames?, verified, reason?}]
frames: number # total frames rendered by ctx.render_animation calls
Comment on lines +413 to +416

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Declare the scene receipt in exec's output schema

Add scene to this schema because exec.py always returns it ("scene": scene_receipt), and the new skill documents it as part of the success payload. As written, a downstream reference such as {{ sequence.scene }} is reported as an unknown output field during lock compilation, and --simulate synthesizes no scene value even though a real run provides one.

Useful? React with 👍 / 👎.


skills:
- headless-rendering.md
- ifc-import-ifcopenshell.md
- look-presets.md
- exec-bpy.md
Loading
Loading