Octov0.11.7
Reference

CLI

The octo command: run, invoke, eval, schema, and version.

The octo binary starts integrations, calls flows directly, evaluates CEL expressions, and emits the capability catalogue the editor and documentation tooling consume.

octo — run and invoke octo integration flows

Usage:
  octo [run] --config <path> [--watch]                       Start connectors and flows (default)
  octo invoke --config <path> --flow <name> [--data <json>] [--vars <json>]
                                                             Call one flow and print its result
  octo eval --expr <cel> [--data <json>]                     Evaluate a CEL expression and print the result
  octo schema [--out <path>]                                 Print the editor capability schema as JSON
  octo version                                               Print the version and build date
  octo --help                                                Show this help

Flags accept one or two dashes: --config and -config are equivalent. Running octo with no arguments prints the help page above.

Subcommands

SubcommandPurpose
runStart the configured connectors and flows until interrupted (the default subcommand).
invokeLoad a config, call one flow by name with a JSON body, and print its result. Use --break-at to stop at a block and inspect the message there.
evalEvaluate a CEL expression against an ad-hoc message, without a config.
schemaEmit the capability catalogue (blocks and connectors) as JSON. capabilities is an alias.
versionPrint the release version and build date.

octo run

Starts every connector and flow declared in the config and keeps them running until the process receives SIGINT (Ctrl-C) or SIGTERM. run is the default subcommand, so octo --config app.yaml is the same as octo run --config app.yaml.

octo run --config samples/hello-world.yaml
FlagTypeDefaultDescription
--configpath(required)Path to the runtime config: a YAML file or a directory.
--watchboolfalseReload the runtime when the config changes.
--storage-dirpath./octo-storeDirectory the object store is serialized into, so objects survive a restart. An empty value keeps the whole store in memory. Volatile and secret objects are never written there.
--observabilitybooltrueServe liveness and readiness probes on the admin port. --observability=false binds nothing.
--observability-addraddress:39999Admin listen address for the probes and metrics. An address already in use is logged and the run continues without them.
--metricsboolfalseAlso serve Prometheus metrics at /metrics on the admin port.
--metrics-blocksaddresses(none)Comma-separated block addresses to add per-block timings for, or * for every block.
--tracesboolfalseRecord a trace of every message. Development feature with significant overhead.
--traces-blocksaddresses*Comma-separated block addresses to trace, or * for every block.
--traces-filepath(auto)Where the standalone runtime writes its JSONL trace. Defaults to octo-trace-<time>-<pid>.jsonl in the working directory.
--traces-bodiesbooltrueInclude message bodies in trace records.
--traces-varsbooltrueInclude message variables in trace records.
--traces-max-payloadbytes32768Drop a captured body or variable map larger than this.
--traces-bufferint4096How many records to queue before dropping.

These configure the observability runtime service, which serves on a port of its own, separate from any http connector. Each flag defaults to the environment variable of the same name (see below), so passing the flag always wins.

--metrics exports per-flow rate, errors and duration, the in-flight count per flow, and the Go runtime and process collectors. It is off by default because per-flow accounting is real work on the flow's own goroutine. --metrics-blocks adds per-block timings for the blocks you name, in the same address grammar --spies accepts:

octo run --config app.yaml --metrics \
  --metrics-blocks 'orders.charge,orders.fanout[audit].log-it'

Metrics tell you a flow is slow. --traces tells you what happened to one message: every flow it entered, every block it passed through, the model turns an agent took, joined by one id that survives flow-refs, splits, aggregates and the hop between processes.

octo run --config app.yaml --traces --traces-file /tmp/run.jsonl
jq -c '{seq,kind,traceId,path,durationNs}' /tmp/run.jsonl

Tracing is a development feature, and the run says so at startup. With the defaults it records two events per block per message and marshals the payload for each, on the flow's own goroutine: around 700ns and 30 allocations per block, against 15ns and none when payload capture is off. See Tracing.

Per-block timings are taken on the flow's own goroutine, so they land on the message's critical path. The counters are exact (nothing is queued or dropped, at any load) and blocks you did not name cost one comparison each. * watches every block in every flow, which makes it a debugging tool rather than a steady-state setting.

When --config points to a directory, every .yaml/.yml file directly in it (sorted by name, subdirectories ignored) is loaded and merged into one config. The config's directory also becomes the resource root that relative resource ids (templates, env files) resolve against.

Files ending in _test.yaml are skipped. orders_test.yaml is a test suite for the flows in orders.yaml, the way orders_test.go tests orders.go.

Once every connector and flow has started, the runtime prints a ready banner to stdout:

time=... level=INFO msg="starting runtime" version=0.11.7 connectors=1 flows=1╭────────────────────────────────────────────────╮│  🐙  octo v0.11.7 is up and ready to roll!  🐙  ││          1 flow · 1 connector serving          │╰────────────────────────────────────────────────╯

Watch mode

With --watch, the runtime watches the config path (and the .env files it consults) and rebuilds on every change: the current generation is stopped, the config is reloaded, and a new generation starts. Failures are not fatal. A config that fails to load, build, or start logs an error and waits for the next change. Runtime services (connections, leases) are built once and reused across reloads.

octo invoke

Loads a config, starts the flows in invoke mode (sources are not started, so no ports are bound and no schedules fire), calls the named flow once with the given body, prints the result message as JSON on stdout, and exits.

octo invoke --config samples/hello-invoke.yaml --flow greet --data '{"name": "Ada"}'
{"event_id":"5c12a0e3f47b4d19a6c8f0b21d3e4a97","body":{"greeting":"hello, Ada!"}}

The result is the whole message (event_id, variables, body), not the body alone, because the variables a flow builds up with set-variable, enrich or foreach are as much its result as the payload. variables is omitted when the flow set none. To pipe just the payload onward:

octo invoke --config orders.yaml --flow orders --data '{"orderId": 42}' | jq '.body'
FlagTypeDefaultDescription
--configpath(required)Path to the runtime config (file or directory).
--flowstring(required)Name of the flow to invoke.
--dataJSONstdinJSON request body. When omitted, piped stdin is read instead.
--content-typeMIME(none)Treat --data as a raw body of this type instead of JSON. See Raw bodies.
--varsJSON(none)JSON object seeding the message variables.
--storage-dirpath(in memory)Directory the object store is serialized into. Unlike octo run this defaults to an in-memory store, so repeated invokes are reproducible; pass it (or set OCTO_STORAGE_DIR) to read or seed the objects a run left behind.
--timeoutduration30sMaximum time to wait for the flow (Go duration, e.g. 10s, 2m).
--break-ataddress(none)Run until this block, print the message it produced, and stop. See Breakpoints.
--spiesaddresses(none)Comma-separated blocks to record every message that crosses. See Spies.
--mocksJSON(none)Blocks to answer for instead of running. See Mocks.
--run-debug-configpath(none)A file holding the whole run. See Debug config.
--envelopeboolfalseAlways print the outcome envelope, reporting a flow failure in it rather than exiting non-zero.
--envelope-outstring(none)Write the envelope to this file instead of stdout (implies --envelope). Use it when the flow itself prints.
--tracesboolfalseRecord a trace of the run: the message entering and leaving every block.
--traces-filepathocto-trace-<time>-<pid>.jsonlWhere the trace is written. The other --traces-* flags apply too.

The body can also arrive on stdin. --data and a pipe are interchangeable:

echo '{"name": "Grace"}' | octo invoke --config samples/hello-invoke.yaml --flow greet
{"event_id":"9f1c3b0e7a5d4e2b8c6f0a1d2e3f4b5c","body":{"greeting":"hello, Grace!"}}

Stdin is only read when it is piped or redirected, never from an interactive terminal, and it must be valid JSON. When the flow drops the message (a filter rejected it, or a block returned nothing), invoke logs flow dropped the message and prints no result. Any flow is callable this way, including one without a source: (see Flow File).

Raw bodies

--content-type opts the body out of JSON, for a flow fed by a raw-content source (XML, CSV, form-encoded). The bytes of --data become the message body verbatim, tagged with the MIME type you give, exactly as an HTTP source configured with rawBody: true would receive them.

octo invoke --config orders.yaml --flow intake \
  --content-type application/xml \
  --data '<order><id>42</id></order>'

Inside the flow the body takes the raw-content shape (body.contentType and body.rawData) rather than a parsed object; see Raw Content. Without --content-type the body is parsed as JSON.

Seeding variables

--data only fills the body, and invoke starts no sources, so nothing populates the message variables for you: the HTTP source is what normally copies request headers into them (see State and Data). A flow that reads vars needs them supplied with --vars:

octo invoke --config orders.yaml --flow orders \
  --data '{"orderId": 42}' \
  --vars '{"x-api-key": "dev-key"}'

The flow now sees vars["x-api-key"] exactly as it would behind a real request. Without --vars, an expression reading a variable that was never set fails with a missing-key error.

Breakpoints

--break-at shows what the message looked like at one point in the flow. The runtime wraps the block you address, runs the flow until it reaches that block, records the message the block produced, and stops. The rest of the chain never runs.

octo invoke --config samples/builtins-demo.yaml --flow demo \
  --data '{"firedAt": "2026-07-10T12:00:00Z"}' \
  --break-at 'demo.set-threshold'
{"reached":true,"block":"demo.set-threshold","message":{
  "event_id":"5c12…","variables":{"threshold":100},
  "body":{"firedAt":"2026-07-10T12:00:00Z","orders":[{"id":1,"amount":50},{"id":2,"amount":250}]}}}

The snapshot is the message after the addressed block ran, which is what the next block would have received: the body and every variable set so far.

Breakpoints are a debugging aid for invoke only. They are not a block you can write in a flow, they never appear in the editor palette, and they are refused outside invoke mode.

The debug envelope

A run that was asked to observe itself, with --break-at or --spies or both, prints one JSON envelope instead of the result message, wrapping what it saw:

FieldMeaning
reachedWhether execution ever got to the block. Present only under --break-at.
blockThe address you asked for, echoed back.
messageThe message the block produced. Present only when reached is true.
resultThe flow's own result message. Present when it was not stopped at a breakpoint.
droppedThe flow filtered the message out and returned nothing.
spiesWhat each spied block saw. See Spies.
errorThe flow's failure, when it failed before reaching the block.

A --mocks-only run prints its result message, like any other invoke, because mocking produces no observations to wrap. Ask for the envelope outright with --envelope.

The outcome envelope

--envelope prints the same envelope for any run, even one that observed nothing. Use it when a program, rather than a person, reads the output.

A plain invoke reports its three outcomes three ways: a flow that completed prints its message, one that dropped the message prints nothing, and one that failed prints nothing and exits 1 with the error in a log line. --envelope says all three in one shape:

octo invoke --config samples/builtins-demo.yaml --flow demo --data '{"firedAt":"x"}' --envelope
{"result":{"event_id":"6708a609…","body":{"firedAt":"x","orders":[…]}}}
{"dropped":true}
{"error":"block \"seed-orders\": upstream exploded"}

Under --envelope, a flow that fails exits 0: the failure is the answer, not a failure of the CLI. A run that could not start (an unresolvable block address, a broken config) still exits non-zero, because there is no outcome to report.

--envelope-out <path>: when the flow itself prints

stdout is not the CLI's alone. A log block writes through a logger connector, whose output defaults to stdout, so a flow that logs interleaves its own JSON with the envelope, and it cannot be quieted from outside (the connector takes its level and destination from the config). --envelope-out writes the envelope to a file instead, leaving stdout to the flow.

octo invoke --config samples/multi-transform.yaml --flow order \
  --data '{"orderId":"A-1","qty":3,"price":10.0}' --envelope-out outcome.json

# stdout is the flow's own log line:
{"time":"…","level":"INFO","msg":"order A-1 subtotal=30 total=33"}

# the answer is in the file:
cat outcome.json
{"result":{"event_id":"…","body":{"orderId":"A-1","subtotal":30,"total":33}}}

This is what dolphin, the test runner, reads, which is why it can test a flow that logs.

A block that is never reached is a normal result, not an error. If the flow takes the other branch of an if, matches a different switch case, or drops the message first, you get reached: false and exit code 0:

{"reached":false,"block":"demo.any-orders[else].log"}

An address that does not resolve is a mistake in your command, so invoke exits non-zero and prints no envelope. The error lists what is there:

no block "nosuchblock" in that chain (blocks: seed-orders, set-threshold, any-orders, each-order, drop-threshold)
has no branch "nope" (branches: then, else)

Addressing a block

An address starts with the flow name and walks down to the block, one segment per step:

<flow>.<block>
<flow>.<composite>[<branch>].<block>
<flow>.<composite>[<branch>].<composite>[<branch>].<block>

A bracket names the branch to descend into. Every segment but the last names a composite and the branch to follow; the last names the target block.

Naming a block

Within a chain, a block is matched by its name, else its type, else its ref, in that order. An unnamed block is still addressable as long as it is the only one of its kind in that chain:

process:
  - type: set-payload
    name: seed-orders     # -> demo.seed-orders
  - type: log             # -> demo.log   (only one `log` in this chain)
  - ref: shared-http      # -> demo.shared-http

If two blocks in the same chain would match, the address is rejected rather than guessed at. Give the one you want a name:.

Naming a branch

Every composite exposes its branches by name:

CompositeBranch names
ifthen, else
foreach, enrich, cache-scopebody
handle-errors, ai-retryprocess, error
validateonReject
split, aggregatebuildResponse
forkeach branch's name, or its index (0, 1, …)
switcheach case's name, or its index, plus default
ai-routereach route's name, plus default
mcp-routereach tool's name
ai-agenteach tool's name, plus events, nameThread and default

A bracket on the flow itself selects its error chain: orders[error].notify.

Examples

Given this flow:

flows:
  - name: orders
    process:
      - type: set-payload
        name: seed
      - type: if
        name: check-header
        condition: "size(body.orders) > 0"
        then:
          process:
            - type: rest
              name: charge
        else:
          process:
            - type: log
      - type: foreach
        name: each-order
        items: "body.orders"
        as: order
        body:
          process:
            - type: switch
              name: classify
              cases:
                - when: "vars.order.amount >= 100"
                  process:
                    - type: log
              default:
                process:
                  - type: log
      - type: fork
        name: fanout
        branches:
          - name: audit
            process:
              - type: log
                name: log-it
          - name: notify
            process:
              - type: rest
    error:
      - type: log
        name: notify-failure
AddressBreaks on
orders.seedThe set-payload, in the flow's own chain.
orders.check-header[then].chargeThe rest inside the if's then branch.
orders.check-header[else].logThe log inside the else branch. Reports reached: false whenever the condition is true.
orders.each-order[body].classifyThe switch, on the first iteration of the loop, where vars.order is the first item.
orders.each-order[body].classify[0].logThe log inside the switch's first case. The loop keeps going until a message actually reaches it, so this fires on whichever iteration first matches amount >= 100.
orders.each-order[body].classify[default].logThe log in the switch's default branch.
orders.fanout[audit].log-itA block inside a fork branch, addressed by the branch's name.
orders.fanout[1].restThe same, by branch index.
orders[error].notify-failureA block on the flow's error chain. Reports reached: false unless the flow fails.

The address may name a flow other than the one you invoke, which is how you break inside a flow reached through flow-ref. The stop folds back through the call, so the calling flow halts too:

octo invoke --config ./flows --flow caller --break-at 'sub.step'

Things worth knowing

  • The block after the breakpoint never runs, so no downstream side effects fire.
  • The block you break on does run. The snapshot is taken after it. If it fails or drops the message there is nothing to show, so you get reached: false with the failure in error.
  • In a foreach, the breakpoint fires on the first iteration that reaches it and the loop stops there.
  • In a cache-scope, a cache hit skips the body entirely, so a breakpoint inside the body correctly reports reached: false. Nothing is written to the cache when a breakpoint halts the body.
  • A fork's sibling branches still run to completion. A breakpoint in one branch does not cancel the others; the flow halts after the fork joins.
  • Numbers in the printed body are JSON numbers, so integers may show as floats.

Spies

--spies records every message that crosses a block and, unlike a breakpoint, leaves the run alone. It takes a comma-separated list of block addresses. A test suite takes the same addresses and adds assertions over what was recorded:

octo invoke --config samples/builtins-demo.yaml --flow demo \
  --data '{"firedAt": "2026-07-10T12:00:00Z"}' \
  --spies 'demo.set-threshold,demo.each-order[body].classify-order'

The envelope grows a spies array, one entry per address, in the order you gave them. Each entry holds the crossings in the order they happened:

FieldMeaning
addressThe address you asked for, echoed back.
recordsOne entry per crossing. Empty when the flow never reached the block.
records[].seqOrders records across every spy, so a multi-block trace reads in the order things actually happened.
records[].atWhen the crossing was recorded.
records[].inputThe message the block received.
records[].outputThe message it returned. Absent when it dropped or failed.
records[].droppedThe block filtered the message out.
records[].errorThe block failed, with this message.

A block that runs many times (inside a foreach body, a fork branch, a cache-scope) reports one record per crossing.

A fork runs each branch on a clone and discards the branch's message, so a block inside one has no way to report through the flow's result. A spy's records leave out of band, which is the only way to see what that block did.

Mocks

--mocks replaces a block with a canned answer, so its real work (an HTTP call, an LLM, a database write) never happens. It takes one JSON object keyed by block address. A test suite takes the same spec in YAML, which is the usual way to keep one:

octo invoke --config samples/error-handling.yaml --flow charge-flowlevel \
  --data '{"amount": 250}' \
  --mocks '{"charge-flowlevel.call-charge":{
     "cases":[{"when":"body.amount > 100","body":{"id":"ch_1"}}],
     "default":{"error":"unexpected input"}}}'

For anything real, put the spec in a debug config file instead of on argv.

The mock spec

FieldTypeMeaning
casesarrayTried in order; the first whose when holds is applied.
defaultcaseApplied when no case matched. Has no when.

Each case has a condition and exactly one outcome:

FieldTypeMeaning
whenCELEvaluated against the message the block received (body, vars, env, …). Required on a case, forbidden on the default.
bodyanyThe body the block returned. A literal value, not an expression.
varsobjectVariables the block set, alongside its body, for blocks that report through a variable such as an HTTP call setting vars.status. Only valid with a body.
errorstringFail the block with this message.
dropboolFilter the message out, as a filter block would.

Things worth knowing

  • A mock replaces the block; it does not wrap it. A wrapped HTTP call would still fire.
  • No matching case and no default fails the block. It does not fall through to the real one, which is gone from the flow, so a "mocked" call can never reach the network. Add a default, or a trailing case with when: "true".
  • A mocked failure is indistinguishable from a real one. The mock takes the target's label, so the error reads block "call-charge": card declined and an error path branching on vars.error.block behaves exactly as in production.
  • Mocking a composite removes everything inside it, which is how you cut out a whole sub-tree. A spy or breakpoint addressed inside a mocked block is rejected rather than silently reporting nothing.

Combining the three flags

They compose on the same block, and the nesting is fixed:

breakpoint[ spy[ mock ] ]

A spy on a mocked block shows you what the flow fed it and what the mock answered, and a breakpoint on top stops the flow there. The mock is innermost because it replaces its target, and the breakpoint outermost because it halts the flow by marking the message, which a spy wrapped around it would record as part of the block's output.

Debug config file

--run-debug-config reads a whole debugging run from one file, so a session is reproducible instead of a page of flags to retype:

debug.yaml
input:
  data: { amount: 250 }
  vars: { x-api-key: dev-key }
breakAt: orders.charge
spies:
  - orders.validate
  - orders.fanout[audit].log-it
mocks:
  orders.charge:
    cases:
      - when: 'body.amount > 100'
        error: card declined
    default:
      drop: true
octo invoke --config orders.yaml --flow orders --run-debug-config debug.yaml
SectionEquivalent flag
input.data--data
input.vars--vars
breakAt--break-at
spies--spies
mocks--mocks

Every section is optional. It is YAML, and JSON is accepted too, so a --mocks blob pastes straight in.

A flag overrides its section, whole. --spies replaces the file's list rather than adding to it.

For the body the order is --data, then the file's input.data, then piped stdin. The file comes before stdin, unlike a bare invoke, so a run fully described in a file never waits on a pipe. A file with no input.data still falls through to stdin.

Unknown keys are rejected, so a misspelled spys: says so rather than silently recording nothing:

parse debug config "debug.yaml": field spys not found in type main.debugConfig

Print the file's JSON Schema with octo schema --kind debug-config.

octo eval

Evaluates a CEL expression against an ad-hoc message. No config and no runtime services are involved, so it is the fastest way to try an expression before pasting it into a flow; see CEL Expressions.

octo eval --expr '1 + 2'
{"ok":true,"result":3}
FlagTypeDefaultDescription
--exprstring(required)The CEL expression to evaluate.
--dataJSONstdinJSON object bound to the body variable. When omitted, piped stdin is read instead.
--varsJSON{}JSON object bound to the vars variable.
--envJSON{}JSON object bound to the env variable.

The remaining message variables get defaults: eventID is a fresh id, correlationID is empty, and now is the evaluation time.

octo eval --expr 'body.orders.filter(o, o.amount >= vars.min).map(o, o.id)' \
  --data '{"orders": [{"id": 1, "amount": 50}, {"id": 2, "amount": 250}]}' \
  --vars '{"min": 100}'
{"ok":true,"result":[2]}

The output is always a one-line JSON envelope:

{"ok":true,"result":<value>}
{"ok":false,"result":null,"error":"<message>"}

ok distinguishes a successful evaluation from a compile or evaluation failure; result is always present so a legitimate false/0/null result is not confused with its absence. Both outcomes exit 0, so scripts should parse the envelope rather than inspect exit codes:

octo eval --expr '1 +'
{"ok":false,"result":null,"error":"compile expression \"1 +\": ERROR: <input>:1:4: Syntax error: mismatched input '<EOF>' ..."}

eval runs without a loaded config, so the templateResource() function has no resources to render and calling it reports resource: not found. Every other part of the expression environment behaves exactly as it does inside a flow. See Octo Extensions.

octo schema

Generates the capability catalogue, the machine-readable description of every registered block and connector (types, labels, categories, settings fields, defaults), and prints it as JSON. octo capabilities is an alias.

octo schema            # print to stdout
octo schema --out capabilities.json
FlagTypeDefaultDescription
--kindenumcapabilitiesWhich schema: capabilities, or debug-config for a debug config file.
--formatenumjsonPrint it as json or yaml.
--outpathstdoutWrite it to a file instead of stdout.

The output is an object with two arrays:

{
  "blocks": [
    {
      "type": "ai-mapping",
      "label": "AI Mapping",
      "category": "processor",
      "group": "AI & LLM",
      "icon": "Sparkles",
      "description": "...",
      "fields": [ ... ]
    }
  ],
  "connectors": [ ... ]
}

This catalogue powers the visual editor's block palette and property forms, and CI's documentation drift check reads octo schema and compares it against the reference pages: every registered block and connector type must be documented by exactly one page.

The debug-config schema

--kind debug-config prints the JSON Schema of a --run-debug-config file, so an editor can complete it:

octo schema --kind debug-config --out debug.schema.json
octo schema --kind debug-config --format yaml     # same document, as YAML
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://octo.run/schemas/debug-config.json
title: octo debug config
type: object
additionalProperties: false
properties:
    input: …
    breakAt: …
    spies: …
    mocks: …

octo version

Prints the release version and, when available, the build timestamp.

octo version
octo 0.11.7 (built 2026-07-09T06:11:43Z)

octo --version and octo -version print the same line. The build date is stamped at link time for released binaries; a go build binary falls back to the VCS commit time, and the suffix is omitted when neither exists.

Environment variables

VariableDescription
LOG_LEVELLog verbosity for the process logger: debug, info (default), warn, or error. An invalid value logs a warning and falls back to info. Logs are written to stderr.
OCTO_ENV_FILEPath to an extra .env file loaded in addition to ./.env, overlaying it. See Flow File: env.
OCTO_STORAGE_DIRDefault for --storage-dir: the directory the object store is serialized into. For octo run it defaults to ./octo-store; for the one-shot octo invoke it defaults to an in-memory store, so a probe is reproducible. Set it to an empty value to keep run's store in memory too. See KV Store and Persistence.
OCTO_OBSERVABILITYDefault for --observability. Accepts true/false, 1/0, on/off, yes/no (case-insensitive). An unparseable value logs a warning and is ignored.
OCTO_OBSERVABILITY_ADDRDefault for --observability-addr.
OCTO_METRICSDefault for --metrics. Same accepted values as OCTO_OBSERVABILITY.
OCTO_METRICS_BLOCKSDefault for --metrics-blocks: comma-separated block addresses, or * for every block.
OCTO_TRACINGDefault for --traces. Same accepted values as OCTO_OBSERVABILITY. This is how tracing is enabled in a deployed pod, which passes no arguments.
OCTO_TRACING_FILEDefault for --traces-file.
OCTO_TRACING_BLOCKSDefault for --traces-blocks.
OCTO_TRACING_BODIESDefault for --traces-bodies.
OCTO_TRACING_VARSDefault for --traces-vars.
OCTO_TRACING_MAX_PAYLOADDefault for --traces-max-payload.
OCTO_TRACING_BUFFERDefault for --traces-buffer.

The OCTO_OBSERVABILITY*, OCTO_METRICS*, OCTO_TRACING* and OCTO_STORAGE_DIR variables are read from the process environment only, not from a .env file: they are resolved when octo run parses its flags, before any config (and therefore any .env) is loaded. Passing the flag wins over the variable.

Config-declared environment variables (the env: section of a flow file) are resolved with the precedence OS environment > .env file > declared default; see Flow File for the full rules.

On this page