Automation: exec, wait, run, history

These operations are implemented by the agent Hub engine. The CLI sends requests; it does not implement expect/send logic locally (except parsing run YAML before calling the API).

Exec

execConsole is one command, including show, ping, and traceroute. The agent joins or opens the Hub, learns the prompt, and completes when that prompt returns after the command was sent. Output is that command's bytes. Privilege and pager-disable run only when the command's own output requires them, or when this Hub already proved them.

eve-iac console exec ./lab n_1 "show version"
eve-iac console exec ./lab n_1 "ping 10.0.0.2"
  • Omitted session_id still uses the Hub. It does not open a second Telnet beside an open console.
  • A session_id that no longer exists is reopened once. console_busy is not expiry.
  • Pass --session when this call must share a specific human lease.
  • Default timeout 10 s; maximum 60 s (timeout_ms on the API).
  • A command that only takes longer than 10 s stays on exec. Pass a larger timeout_ms. Do not move it to run.
  • These are system consoles. The node may still be booting, or waiting on Press RETURN. On the first client of a Hub, one newline is sent only after about 5 seconds of silence with no prompt. Boot text restarts that wait. A Hub that already has another client is not sent that newline.
  • Connect timeout 5 s.
  • Success includes prompt, phase, and rx_to. recovered is true when the session was reopened.
  • command_timeout is recoverable by retrying exec with a larger timeout_ms. auth_required is not.

SDK equivalent (Python):

client.exec_console(ExecConsoleRequest(**{**packed.payload, "node": "n_1", "command": "show version"}))

execConsoleMany runs the same or independent read-only commands on several nodes and joins each Hub. One target failure stays on that target.

runConsole is a real multi-step dialogue (a login with a secret you provide, a wizard, a config exchange). It is not the path for a single show, ping, or traceroute.

Wait

Wait is event-driven console matching — not polling, and not how show, ping, or traceroute finish. Those commands use exec.

  1. Scan retained Hub history.
  2. Then block on live console data until the pattern matches or time expires.
Property Value
Pattern Go regexp
Spanning reads Matches may span Telnet read boundaries
Max timeout 600 s (timeout_ms)
Output Bounded; timeout is distinguishable from cancel/error

CLI:

eve-iac console wait ./lab n_1 \
  --expect 'Router[#>]' \
  --timeout-ms 180000

Python:

client.wait_console(WaitConsoleRequest(**{
    **packed.payload,
    "node": "n_1",
    "pattern": r"Router[#>]",
    "timeout_ms": 180_000,
}))

TypeScript:

await client.waitConsole({
  ...packed,
  node: "n_1",
  pattern: "Router[#>]",
  timeout_ms: 180_000,
});

Go:

client.WaitConsole(ctx, eveiac.WaitConsoleRequest{
    Lab: packed.Lab, Node: eveiac.Ptr("n_1"),
    Pattern: eveiac.Ptr(`Router[#>]`), TimeoutMs: eveiac.Ptr(180_000),
})

Run / expect-send

Run executes an ordered list of steps on a Hub lease. Each step may expect a regexp, send text, or both.

Example YAML file (bootstrap.yaml):

steps:
  - expect: "Press RETURN"
    send: ""
  - expect: "Router>"
    send: "enable"
  - expect: "Password:"
    send_secret: "example-placeholder"
  - expect: "Router#"
    send: "configure terminal"

CLI:

eve-iac console run ./lab n_1 bootstrap.yaml

Step semantics

Field Meaning
Omitted send Do not send anything (expect-only step)
send: "" Send RETURN (empty line)
send_secret Send value; never echoed in responses or transcripts
timeout_ms This step's wait. Default 10 s. A larger value is honored, up to 600 s.

An overall timeout_ms, if set, caps every step (maximum 600 s). Omit it to use the step budgets. On failure, failed_step is 0-based (-1 when all steps succeed). Secret output is redacted in API and MCP responses.

Run uses the agent engine. The CLI only parses YAML and calls runConsole.

Python:

from eveiac import ConsoleRunStep, RunConsoleRequest

client.run_console(RunConsoleRequest(**{
    **packed.payload,
    "node": "n_1",
    "steps": [
        ConsoleRunStep(expect="Router>", send="enable"),
        ConsoleRunStep(expect="Password:", send_secret="example-placeholder"),
        ConsoleRunStep(expect="Router#", send="configure terminal"),
    ],
}))

History

Hub history is a bounded tail of raw console bytes (after Telnet IAC filtering).

Layer Limit
Hub ring 64 KiB (65536 bytes)
MCP default request 8 KiB
MCP hard cap 32 KiB
CLI --bytes 1–65536

There is no persistent log collector. History is available only while the Hub exists (including the 60 s idle window after the last client leaves). end_seq is the newest retained receive sequence. after_seq returns only newer bytes and sets truncated when that sequence has left the ring. History is the ring tail, not the result of the command that just ran (rx_to on exec).

CLI:

eve-iac console history ./lab n_1 --bytes 16384

Python:

client.get_console_history(GetConsoleHistoryRequest(**{
    **packed.payload,
    "node": "n_1",
    "session_id": sess.session_id,
    "max_bytes": 16_384,
}))

MCP eve_iac_console_history applies the same bounds at the VSIX boundary (default 8 KiB, max 32 KiB) and sets truncated when output is cut.