Skip to content

Console

mklang console is the agent-first front door: type what you want, the console's agent picks — or authors — a machine, commissions it, and streams the run state by state. The agent itself is a machine (agent.mk): read it, check it, lint --llm it, scenario-test it, or swap it out entirely.

pip install 'mklang[console]'
mklang console                 # DeepSeek by default; --provider anthropic|openai|…

Layout

┌ mklang console ──────────────────────────────┬─ inspector (F2) ────────┐
│ READY · deepseek · tokens 922+212 · session …│                         │
│ you: create a machine that triages my CSV    │ [Context|Trace|Session] │
│ agent: created triage_csv.mk and ran it: …   │ …                       │
│ ▼ console_agent                              │                         │
│ │  ● decide [ok] → author                    │                         │
│ │  ● author [ok] → save                      │                         │
│ │  ● save   [ok] → decide                    │                         │
│ │  ● do_run ┬ ▼ triage_csv …                 │                         │
├──────────────────────────────────────────────┴─────────────────────────┤
│ > _                                                                    │
└────────────────────────────────────────────────────────────────────────┘

Conversation is the primary workspace, with the bounded live activity tree of the current turn beneath it (brain states under console_agent; Textual draws a single expand toggle ▶/▼ per expandable row — run labels are the machine name only). Each commissioned run nests under the state that launched it; call: sub-runs by depth; fan-out branches as leaves. Normal state output stays in the inspector; only exceptional/truncated previews expand the tree. F2 toggles the inspector (docked at 100+ columns, full workspace below that), ctrl+t toggles activity, ctrl+g requests a cooperative stop after the current state, and ctrl+l clears the conversation.

Conversation rendering

The log and activity tree separate UI chrome from untrusted content (user text, agent prose, tool observations, event previews) — same discipline as best practices (surface layer, no Rich-markup interpolation of model/user text):

Channel How it is shown
Agent reply (status=done) CommonMark via Rich (**bold**, lists, fenced code, links)
User / HITL answers Plain text (no Rich markup interpretation)
Slash results (/run, /check) Fenced json (not full-document Markdown)
/read machine source Fenced yaml
Labels (you:, agent:, errors) Rich markup only for internal chrome strings
Activity tree (turn title, machine, state, output preview) Plain Text segments with fixed styles — previews are not Markdown

Session history and the JSONL transcript stay plain text for audit; only the display path renders Markdown. Square brackets in model output (array[0], [b]…[/b]) are not treated as Rich tags on either the log or the tree.

The agent

One user turn = one run of agent.mk (ReAct-shaped): decide routes between DISCOVER (list machines), RUN (commission one), CLARIFY (ask you), AUTHOR (write a new .mk into the workspace, validate it, repair on errors) and REPLY. Escalations from a commissioned machine, tool-consent prompts and turn-budget exhaustion all come back to you through the input line.

Swap the brain with --agent your_brain.mk — any machine honoring the same tool contract (list_machines, describe_machine, read_machine, check_machine, write_machine, run_machine, ask_user).

Brain prompt assembly

Generative states on the brain follow the host mapping (Best practices §3):

Face Role for the console agent
structure Output shape of this step (e.g. one-line DISCOVER/RUN/…, final reply)
execution Sticky policy (no fake web search, truncation honesty, clock REPLY rules)
prompt Turn data only: {{today}} / {{now}}, {{history}}, {{user_message}}, {{observation}}

Wall-clock questions (“che ore sono?”) use host-filled now via REPLY — the brain must not AUTHOR a machine solely to read the clock.

Slash commands (bypass the agent)

command effect
/machines list commissionable machines with contracts
/run <name> [k=v…] commission directly (--set-style JSON coercion)
/check <name> / /read <name> validate / show a workspace machine
/budget <n> default token budget for commissioned runs
/resume [n] list / finish the session's parked turns
/session current session facts
/help · /quit help · exit

Ctrl+C performs a clean shutdown: an active run is cancelled, any pending human prompt is released, and the console waits for the provider worker to stop before returning to the shell. Ctrl+G only requests cancellation of the current run and keeps the console open. The lifecycle contract is maintained in Best practices §14.

Slash commands use shell-style quoting, so /run demo task="hello world" keeps the value together. Command names are suggested while typing.

Sessions

Every conversation persists under $XDG_STATE_HOME/mklang/console/sessions/<id>/ (default ~/.local/state/mklang/console/sessions/<id>/): state.json (history, spend, tool consents — rewritten atomically per turn), transcript.jsonl (turns + every engine event, streaming append), and checkpoints/ for turns parked on budget exhaustion. --continue reopens the latest session; --session <id> a specific one. The canonical host layout is maintained in Best practices §13.

History for the brain is windowed (ADR 0017): the full conversation remains in the session audit / transcript, but only a tail of recent turns (and a char cap) is injected as {{history}} into agent.mk, with an explicit …[history_truncated…]… marker when anything is dropped. This keeps long sessions from exploding the brain prompt.

Web search from the console

Live web/news questions need a machine with a real tool: search state (not generative prose that pretends to search). Host binding:

Setup Effect
TAVILY_API_KEY=… in .env Tavily auto-enabled for the search tool
MKLANG_SEARCH_BACKEND=fake Deterministic offline hits (demos/tests)
MKLANG_SEARCH_BACKEND=stub Force offline even if a Tavily key is set
unset key + unset backend Structured stub: "no external search bound…"

Example workspace machine: machines/news_search.mk (topic → search → brief). Pattern references: examples/research_web.mk, examples/research_compress.mk.

Tool consent is not an error. The first time a machine uses host tools (search, calc, …) the console pauses with a yellow prompt and asks you to allow it for the session. Type y / yes / and Enter. Afterwards it is remembered in the session (inspector: consented tools). Enter alone means no.

Observations from run_machine (anti-cutoff honesty)

The brain sees a compact JSON observation of each commissioned run, not the full engine trace. That observation is still honest about cutoff:

Field Meaning
truncated A produce step hit max_tokens/length (ADR 0018)
finish_reason Provider stop reason when known
trace {steps, truncated, truncated_steps:[{state, finish_reason?}]}
result_truncated Observation budget clipped a long result string
result May end with …[truncated] when clipped (ADR 0017 style)

Full events still stream to the activity tree / session transcript. The agent is instructed not to invent the missing tail of a truncated result, and not to answer live-web questions from training knowledge alone.

Time-sensitive workspace machines should declare context.today: ""; the host fills today's ISO date before the run (same convention as CLI/MCP). Wall-clock questions need context.now: "" (local ISO datetime). The bundled brain already declares both — see Brain prompt assembly.

Security model

The console inherits the SPEC §11 posture: authored machines are confined to the workspace (--workspace, default ./machines when present, else the XDG user machines dir — path-resolved, no traversal); running a machine whose states invoke host tools (including search if a machine uses it) asks consent once per tool set (remembered per session); provider keys stay in the host environment. The console cannot edit files outside the workspace, run shell commands, or touch git — it is an operational surface, not an IDE (ADR 0015). Workspace FS is class 2 (.mk authoring only); generic data FS / bash are out of core (host plugins only — Best practices §13). Session transcript.jsonl is surface audit, not a substitute for host ops logging (§12).

For other clients

The same live events the console renders are available to every MCP client: mklang-mcp's run/resume stream them as mklang.event logging notifications (ADR 0019) — an external front-end needs nothing more.