Practical guidance for building and debugging SLOP providers. These are recommendations for SDK implementers and app developers, not protocol requirements.
During development, the most common question is "what does my tree actually look like?" A provider should offer a way to print or inspect the current tree without requiring a connected consumer.
Every SDK should expose a human-readable tree dump. This is the single most valuable debugging tool — it answers "did my registration produce the tree I expected?" without any external tooling.
Python:
slop = SlopServer("my-app", "My App")
# ... register nodes ...
slop.refresh()
print(slop.print_tree())TypeScript (server):
const slop = createSlopServer({ id: "my-app", name: "My App" });
// ... register nodes ...
console.log(slop.printTree());TypeScript (browser):
const slop = createSlop({ id: "my-app", name: "My App" });
// ... register nodes via useSlop ...
console.log(slop.printTree());Expected output format — compact, indented, showing node types, properties, affordances, and metadata:
[root] my-app
[collection] todos (count=3, done=1) actions: {add(title: string)}
[item] todo-1 (title="Buy milk", done=false) actions: {toggle, delete} salience=0.8
[item] todo-2 (title="Read spec", done=true) actions: {toggle, delete} salience=0.3
[item] todo-3 (title="Ship it", done=false) actions: {toggle, delete} salience=1.0
[status] settings (theme="dark") actions: {set_theme(theme: string)}
This format should match what formatTree() produces in the consumer SDK — the same output a consumer uses to present the tree to an LLM.
For debugging protocol-level issues, SDKs should also support dumping the raw wire-format tree (JSON). This is useful when the compact format hides a problem — e.g., a content_ref that doesn't serialize correctly, or a meta field with an unexpected shape.
import json
print(json.dumps(slop.tree.to_dict(), indent=2))console.log(JSON.stringify(slop.getTree(), null, 2));Affordance parameters use JSON Schema. Invalid schemas don't cause protocol errors — they pass through to the consumer and may fail at LLM call time (some providers like Gemini are strict about schema completeness).
SDKs should warn at registration time when a parameter schema is likely invalid:
type: "array"withoutitems— will fail on Geminitype: "object"withoutproperties— ambiguous; the LLM won't know what to pass- Unknown
typevalues — likely a typo
These are warnings, not errors. The protocol intentionally does not restrict schemas — but catching common mistakes early saves debugging time.
[slop] Warning: action "create" on "contacts" has param "tags" with type "array"
but no "items" schema. Some LLM providers require "items" for array params.
For debugging connection and protocol issues, SDKs should support logging all messages sent and received. This should be opt-in (off by default) and configurable.
Python:
import logging
logging.getLogger("slop").setLevel(logging.DEBUG)TypeScript:
const slop = createSlopServer({ id: "my-app", name: "My App", debug: true });When enabled, log each message with direction and type:
[slop] → hello {provider: {id: "my-app", name: "My App"}}
[slop] ← subscribe {id: "sub-1", path: "/", depth: -1}
[slop] → snapshot {id: "sub-1", version: 1, tree: {...}}
[slop] → patch {subscription: "sub-1", version: 2, ops: [{op: "replace", ...}]}
[slop] ← invoke {id: "inv-1", path: "/todos", action: "add", params: {title: "New"}}
[slop] → result {id: "inv-1", status: "ok"}
- Call
printTree()— is the node in the tree? - If not: check that
register()was called andrefresh()was called after (server SDK) - If yes: check the consumer's subscription
depthandpath— a shallow subscription may not include deep nodes
- Check the wire-format tree — is the affordance present with the correct
actionname andparams? - Enable message logging — does the
invokearrive? Does theresultindicate an error? - Check that the handler is attached to the correct path — a handler on
"todos/add"won't fire for an invoke on"/todos"with action"add"
- Verify
refresh()is being called after the mutation (server SDK) - Check that the tree actually changed — if the descriptor function returns the same tree, no diff means no patch
- On the browser SDK: verify the component re-rendered (the
useSlophook re-registers on every render)
- Use
meta.salienceto mark less-important nodes — consumers can filter bymin_salience - Use windowed collections for large lists — expose a
windowwith a subset of items - Subscribe at a shallower
depth— get the overview without the details - See Scaling for depth truncation, compaction, and view-scoped trees