XDOG

Tools for building AI agents and managing LLM deployments.

GitHub →

xdog / Packages / flow / Features

Current Flow capabilities and their limits, grounded in the typed workflow runtime and CLI. Generation is not universal dependency freedom; see Reference for deployment caveats.

Modeling

Node-private ports
Typed inputs/outputs wired by explicit edge mappings — no shared flat state.
JSON Schema ports
A port declares a JSON Schema (scalar or nested object/array) plus one `required` flag.
Type-native wire
Port values are live Python (int/float/bool/list/dict), not stringified — structure flows between nodes intact.
$in / $output nodes
Reserved source + sink: state seeds $in; edges to $output collect the result. `entry` is optional (derived from $in).
Conditional edges
equals / contains / numeric gt / gte / lt / lte / and / or / not guards over a source output port.
Bounded loops + cycle detection
Cycles use bounded loop.max with optional when, or strict while with its own condition/bound; exhaustion is a normal bounded stop or strict non-convergence error.
Script nodes
Inline code or a run: module:func reference, imported with the workflow dir on path.
Sub-workflows
A type:"subflow" node runs another workflow as one opaque node — inline or a `./child.json` path ref; ports are derived from the child's signature.
Edge type checking
An edge requires the source and destination port types to match — including a sub-field's type via its schema.
Typed workflow signature
$in carries an optional in_schema (else it's inferred from consumers); the $output signature is derived from the sink edges — so a workflow has a checkable I/O type.
Validate before running
Unknown ports, unfed required inputs, ambiguous producers, unbounded cycles, schema mismatches and prompt-root typos fail before node execution; custom imports may still have effects.

Data flow

JSONPath interpolation
A prompt reads a field with `{{ $.plan.tasks[0] }}`; both engines share one jsonpath-ng evaluator.
Strict interpolation
Prompt/condition roots are checked against declared ports; supported JSONPath sub-fields use the source schema, but not every complex path is statically typable.
Sub-field edge mapping
An edge maps a nested field: `"map": {"$.verdict.within_budget": "flag"}`, type-checked against the source schema.
Structured $in seed
Initial state carries type-native JSON; a structured seed stays a dict/list, not a Python repr.

Execution

Shared frontier kernel
Interpreter and generated Python run the same completion-driven frontier scheduler: stable parallel readiness, conditional inputs, AND joins, and heterogeneous bounded-loop groups.
Dynamic fan-out
A fan_out edge maps a node over a runtime-sized array (once per element, in parallel); fan_in:list preserves index-ordered values and fan_in:concat flattens array results one level. The whole fan group is one checkpoint unit.
Node/driver separation
Node functions consume typed inputs and return outputs; a driver owns retry, storage, memo, budget and checkpoint. Scripts/tools may still have external effects.
Concurrency caps
max_concurrency bounds how many nodes run at once; a separate fan_max_concurrency bounds instances within one fan-out (both default to unlimited).
Structured run result
CLI and generated Python print one success/message/output/context envelope with workflow/run id, timing, tokens, and last node; execute() retains the detailed internal runtime container.
Structured event stream
on_event streams NodeStarted / NodeFinished / NodeFailed with per-node duration and tokens.
Metrics aggregation
A MetricsCollector consumes the event stream into a per-node + per-run snapshot (runs, duration, tokens, failures).
Cost budget
execute(max_tokens=N) aborts a run with WorkflowBudgetExceeded once cumulative agent tokens pass the ceiling.
SDK streaming dry-run
run --dry-run echoes DRYRUN:<model> for SDK nodes only; scripts/CLI backends still execute, structured submission may fail, and generated modules have no equivalent switch.

Resilience

Per-node retry
A RetryPolicy(max, backoff) retries a failed node before giving up (default: fail-fast). Retry lives in the driver.
Failure isolation
on_error:isolate records a failed branch in runtime.failed and skips only its sub-tree.
Checkpoint & resume
A CheckpointStore persists coherent frontier batches by run id; resume restores outputs, completion, loop counters, trace, memo, and token usage with at-least-once batch semantics.
Human-in-the-loop
A human node pauses awaiting a named signal; deliver it and resume the run to continue.
Deterministic reuse
deterministic:true reuses recorded output by (node, input hash); it does not roll back effects or close an effect/checkpoint crash window.

Agents & tools

Host-owned SDK tools
Each executor creates a fresh registry: echo, SDK defaults bash/filesystem/current_time, and opt-in submit_result. Node tool selection and structured injection use the existing Agent loop; no global runtime inventory.
Structured SDK submission
SDK and generated SDK nodes pass generic result_schema/result_sink; validation success writes result_sink['result']. A single structured port accepts its native value, multiple ports split an object; CLI backends use native schema flags.
Installed Flow tool
The application-scoped flow capability invokes non-interactive public CLI argv without a shell and returns exit_code/stdout/stderr. No Flow daemon; manual build stays outside the tool. Normal host permissions apply.
Skill discovery, explicit activation
Installed metadata advertises the flow skill without loading its body or granting tools. Explicit activation (including a node's skills list) loads instructions; discovery starts no workflow or process.
Agent multi-output ports
An agent fans its submit_result object across several typed output ports by field name — each wired independently.
Derived output schema
An agent's structured-output contract is derived from its output ports (no separate output_schema), validated by fastjsonschema.
Agent web search
SDK agent nodes can enable a built-in web_search tool with its own browsing model.
JSON-declared custom tools
A module:func tool manifest, loaded at run and generate time.
CLI agent backend
An agent node can set backend:"claude-cli"/"codex-cli" to run one turn by shelling out to a coding-agent CLI instead of the in-process SDK — no provider/API key needed (the CLI owns auth); structured output maps to the CLI's native schema flag.
CLI tool configuration
Nonempty allowed_tools is forwarded to Claude; Codex has no per-tool allowlist in this adapter. Empty does not mean verified deny-all. External CLI tools/MCP are separate from Flow's SDK registry.
Per-node MCP servers
A CLI agent node declares mcp_servers (an opaque pass-through spec); flow generates the CLI's MCP config, with ${ENV} secret interpolation — the JSON carries the reference, never the token.

Scheduling

Active / timer scheduling
A schedule:{mode:"timer"} block fires the workflow on an interval or cron expression; xdog-flow scheduling install writes a Linux systemd user timer; the CLI does not install an automatic crontab fallback.
Passive / hook scheduling
A schedule:{mode:"hook"} block configures event-to-signal routing. The live entry point serves loopback HTTP; accepted file/socket metadata is not deployed watchers/socket activation.
Shared hook listener
Hook entries share one listener, but the rendered module name and /usr/bin/python3 environment require operator correction/review. HTTP 202 after spawning is not durable execution or workflow completion; no built-in HTTP auth.
Scheduling lifecycle
scheduling install/list/start/stop/uninstall manages local registry and systemd state. --dry-run skips deployment but still loads trusted code; list is registry facts, not live next-fire status.

Codegen & authoring

Code generation
Compile typed JSON to Python with inlined helpers and shared graph semantics. SDK nodes import xdog.agent/xdog.ai; subflows call xdog.flow at runtime; custom imports still apply.
Interpret == compile
Interpreter and generated module agree node-for-node — enforced by a cross-engine parity suite on every feature.
Portable bundle
generate --portable vendors selected sources: SDK nodes/tool manifests select ai/agent, subflows add flow. This is not a recursive child/custom dependency closure; inspect requirements. --offline downloads selected wheels at build time.
Runtime overrides
The generated module honours FLOW_INPUTS (JSON merged into $in) and FLOW_PROVIDER — parity with the interpreter's --input / --provider.
Human + Agent authoring
xdog-flow build edits the same Git-friendly JSON that Coding Agents generate through the Flow skill; a local Web UI is the next editor surface.
Four diagram renderers
Text listing, layered ASCII, Graphviz SVG (with fallback), and Mermaid; node boxes are colour-coded by type (agent/script/human/subflow).

Testing

Companion test suites
xdog-flow test runs a workflow's <name>.test.json — a workflow path, a suite, or a whole directory; exit 1 on failure drops it into a pre-commit hook or CI.
Stub only the boundaries
Agent turns (SDK and CLI alike), human signals, and whole subflow nodes are stubbable; script nodes need an explicit --allow-script-stub. Edges, conditions, loops, fan-out, coercion and $output collection always run for real.
Stubs on the production path
A stub is injected at the provider call, after prompt interpolation and before output parsing — so it is validated by the node's own required-field check and coercion, and a broken {{ $.path }} still fails.
No live agent fallback
StubRunner answers every agent node, including CLI backends; missing stubs fail, not dial out. Unstubbed scripts and imported custom code can still use files, processes and the network.
Concurrency-safe selectors
A stub rule selects on when (deep-subset match on inputs), index (fan array position), or round (activation ordinal) — never on completion order, so a fan-out case is not flaky.
Three-way outcome + calls
expect takes one of success / error substring / paused-at-node, plus a deep-subset output match and a calls map that covers executed and skipped nodes, fan instances, and loop iterations in one number.
Suite validated at load
A stub aimed at a missing node, the wrong node type, or an undeclared output port fails before anything executes — and a selector that never fires is a failure, not a silent fall-through.