# Creating custom nodes with Penny

> Create a reusable node with Penny. Define its interface, review generated code and warnings, test known cases, and revise it while preserving its contract.

Source: https://kaitoi.io/docs/studio/penny-custom-nodes/
Section: Kaitoi Studio / Penny
Written for: Anyone asking Penny to create or adapt a node in Studio.
Last reviewed: 2026-10-24

---

When an existing node does not provide the operation you need, ask Penny to
create one. It can write a custom node's Python and interface, save the script,
and add an instance to your graph. You can describe the behavior in plain
language; you still need to review and test what was built.

## Decide what belongs in the node

Ask Penny to check available nodes first. A custom node is useful for a
specific transformation, calculation, service integration, or reusable tool
with its own controls. If existing steps already do the job, connecting them
may be enough; see [Building and editing graphs with Penny](/docs/studio/penny-graphs/).

Keep the first version focused. Describe one operation and its inputs and
outputs before adding a complex interface, network calls, or several optional
behaviors. This makes the result easier to test and reuse.

## Open the right authoring context

Use **Assistant** in the graph toolbar. Say whether you want a new node, a
separate variation, or an edit to an existing custom node. For an edit,
identify both the graph instance and its script path; ask Penny to confirm
the active script before changing it.

Assistant node generation and manual code editing are separate capabilities.
Depending on your account, Penny may be able to generate a node even when
raw code editing is unavailable. The desktop graph toolbar's **Code** control
and a node's **Edit script** action provide code access where supported. Built-in nodes offer a
clone-and-edit flow; cloning creates a user copy. Mobile Penny does not expose
every desktop authoring tool.

## Write a brief with a testable contract

Specify input types, required or optional inputs, controls with defaults and
ranges, outputs, and clear failure behavior. Include whether the node may use
network services or install dependencies. For example:

> Create a separate “Frame an image” node. It takes one required image and
> adds a solid border outside it, keeping the original pixels. Add an integer
> inset control from 0 to 64 pixels, default 24, and a frame-color control.
> Return a PNG image and a short settings summary. Missing images or invalid
> inset values must fail clearly without a success image. Use no model or
> network service. Save it in a new package, add one instance, and report its
> actual script path. Build only; do not run it.

State which settings should be editable after creation. Avoid relying on a
value buried in the generated code when you need to change it regularly.

## Review the proposed interface


The illustrative **Frame an image** contract has a required image input,
an inset control from 0 to 64 pixels with default 24, a frame-color control,
and image and summary outputs. **Interface** describes the operation before
testing. **Expected output** illustrates an outside border, with the original
image preserved. **Missing image** expects a clear failure and no success
image. Every state describes intended behavior, not a completed run.

Authored artwork and concept cards are not actual Studio panels or a measured
pixel preview. The controls create no node and execute no code.

*Figure: Explore an example custom node's interface, intended output, and missing-input behavior.*

Inspect names, types, defaults, ranges, and the declared outputs on the actual
node. Check that required inputs and error behavior match the brief. A
wire supplies a connected input's value; an input that looks different from
the brief may need a schema correction, rather than just another prompt.

## Keep the script and graph identities clear

Ask Penny to report the saved script path and the node instance it added.
A saved script and a placed graph node are different outcomes. If saving
succeeds but placement fails, have Penny inspect the saved node's catalog
entry before creating another script. A filename conflict may produce a
suffixed name; use the path actually returned by the tool.

Before modifying reused code, preserve a copy of the source or ask for a new
package. Instances can refer to the same script path. Do not assume that
selecting one instance, renaming its label, or copying a graph isolates the
script you are about to overwrite. Preserve the graph separately through
[Saving and recovery](/docs/studio/saving-and-recovery/).

## Read source checks and warnings

Generated node saves apply source and contract checks. If a save is rejected,
read its error, line information, and suggested correction; ask Penny to fix
that specific problem and save again. Do not assume the proposed code became
the saved version after an error.

Successful saves can also carry implementation warnings for patterns such as
unfinished code, placeholder bodies, or exception handling that returns
replacement content as success. Ask Penny to explain any warning. **Static
checks do not execute the node**, and absence of warnings does not prove its
behavior is complete or correct.

## Use the supported node contract and runtime

For ordinary Python nodes, preserve comment-based metadata, input and output
declarations, and a top-level `run()` function. Return an output dictionary
with keys matching the declared outputs. Renaming a pin or changing its type
can break an existing connection; keep the interface stable unless the change
is intentional. See [Writing custom nodes](/docs/core/custom-nodes/) for code.

Ask Penny to use supported runtime helpers for files, output media, package
assets, and service requests. User-node policy restricts direct filesystem
access and backend internals; copying built-in Python may need adaptation.
Declare needed packages through supported requirements metadata and handle
unavailable dependencies visibly. Keep credentials out of source; configure
services through the intended integration settings.

## Plan the smallest useful tests

Use **Manual** evaluation on the test node and other affected nodes when you
want to control execution; eligible Auto nodes can run after changes. Keep
the construction request explicit about whether running is allowed. Building
can still use a paid assistant model, even if the node needs no model call.
Provider tests can incur separate usage; see [Credits and billing](/docs/reference/credits/).

Choose a small known input and write down the expected output before running.
Include a boundary value and a failure case. For media, inspect the produced
file as well as its path or preview; a valid-looking filename is not evidence
that useful media exists.

## Compare expected test outcomes


These are independent planned tests, not recorded results. With an 800 × 600
image, **Known input** uses a 24-pixel outside border and expects 848 × 648.
**Boundary value** uses zero and expects 800 × 600, with no added border.
**Invalid value** uses −1 and expects a clear error without a success image.
Also test a missing image, as shown above. A control's minimum does not
replace checking values that can arrive through a wire or code.

The figure computes no media, executes no Python, and makes no provider call.
Use the actual node's results to decide whether its implementation passes.

*Figure: Compare a known input, a boundary value, and an invalid value for the example frame node.*

## Run and inspect the actual result

Ask Penny to run the specific test node with the agreed inputs. Its required
upstream dependencies may be included, and eligible cached results can be
reused. Identify what executed, what was reused, and what remains unverified;
see [Runs](/docs/concepts/runs/).

Compare actual dimensions, format, content, and the summary with the expected
values. A successful execution can still return the wrong result. For a
failure case, check that it reports the problem instead of silently creating
sample content. Save useful output through [Files and results](/docs/studio/files-and-results/).

## Revise and reuse deliberately

Give Penny the observed result, expected result, exact error, and relevant
script path. Ask for a narrow fix that preserves working inputs, outputs,
and connections. After saving, inspect the actual node interface and repeat
the relevant tests. Add a fresh instance if you need to compare its current
schema with an older instance; do not delete the working copy first.

Save the graph and retain the intended source/package and required assets.
Creating a private node does not publish it to the community or guarantee that
another account has its dependencies, services, or permissions. Verify those
requirements before sharing or relying on it elsewhere.
