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.mkl):
read it, check it, lint --llm it, scenario-test it, or swap it out entirely.
pip install mklang # the console ships by default since 0.15.0
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.mkl 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. The status strip reports provider, session token spend, and
current phase; READY, RUNNING, WAITING, STOPPING, and ERROR are
distinct operator states.
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.
The shell uses semantic operator states rather than color alone: ● READY,
◐ RUNNING, ⏸ WAITING, ■ STOPPING, and ! ERROR. The input border changes
to an amber answer mode for consent, HITL, and budget questions; disabled input
means the worker is still active. The inspector starts with explicit empty
states, then exposes the latest context, trace, and session facts. On narrow
terminals F2 changes the inspector from a docked panel into the full workspace.
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.mkl (ReAct-shaped): decide routes between
TASK_UPDATE (the durable goal/plan/progress ledger),
WORKSPACE_SCAN / SEARCH / READ / ANALYZE (read-only project inspection),
DISCOVER (list machines), RUN (commission one), CLARIFY (ask you),
AUTHOR (write a new .mkl into the workspace, validate it, repair on
errors) and REPLY. A project-analysis request must inspect the workspace
before replying; the final answer is expected to distinguish observed facts,
inferences and uninspected areas. Escalations from a commissioned machine,
tool-consent prompts and turn-budget exhaustion all come back to you through the
input line.
The default brain can use list_workspace, read_workspace_file and
search_workspace for visible UTF-8 project files. Reads are relative to the
configured workspace and exclude hidden, build, vendor and cache directories;
they never grant shell or write access. Each operation is bounded (400 listing
entries, 120,000 bytes per read, 80 search matches, 2,000 files and 64 MiB per
search), skips obvious credentials/keys/databases, and reports truncation or
skipped content. Explicit project-analysis turns are host-guarded: the brain
cannot finish until workspace evidence and an evidence brief exist. Swap the
brain with --agent your_brain.mkl — custom brains
that want project inspection should declare and use the same workspace tools in
addition to the existing contract (list_machines, describe_machine,
read_machine, check_machine, write_machine, run_machine, ask_user,
update_task).
The task ledger is updated explicitly through update_task; its validated
fields are persisted in state.json and include the current goal, phase,
plan, progress, blockers, artifacts and verification evidence. Invalid brain
decisions enter a bounded repair state rather than being silently treated as a
reply. Authoring validates the complete machine before an atomic replacement,
so a failed validation cannot partially overwrite an existing file.
Tool consent is scoped to the commissioned machine and tool (machine:tool),
not only to a global tool name. Machine discovery exposes conservative risk
metadata (read_only, external_egress, irreversible, sensitivity,
idempotent); these are host policy hints and cannot be self-granted by the
brain.
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}}, {{workspace_context}}, {{workspace_brief}}, {{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 agent turns and 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. Every /run argument must be key=value; malformed
arguments are rejected before the worker starts. Command names are suggested
while typing, and /help includes copyable examples.
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, task ledger, and the session's
always yes choice — 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 for the selected workspace and replays recoverable user, agent,
and slash-result records into
the conversation pane; a torn final transcript line is ignored. Unexpected
worker errors are shown as actionable ERROR state and re-enable the prompt.
--session <id> reopens a specific one only when it belongs to the selected
workspace. 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.mkl, 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…" |
Project machines live in the workspace's canonical machines/ directory. The
console discovers, reads, runs, and authors machines there; legacy .mkl files
in the workspace root remain readable for compatibility, but new authored files
are always saved under machines/. Example workspace machine:
machines/news_search.mkl (topic → search → brief).
The stdlib std_research (search → ground) is always runnable by name.
Pattern references: examples/research_web.mkl, examples/research_compress.mkl.
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 / sì and Enter.
Type always yes to approve this and future low-risk confirmation prompts
in the session, including budget-continuation prompts. High-risk prompts for
external egress, writes, irreversible actions, or unknown third-party tools are
always shown again. The choice is
persisted when the session is reopened with --continue or --session.
Afterwards tool consent 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.
Search observations are also bounded before they are accumulated into a
machine's working notes: titles are capped at 300 characters, URLs at 500, and
snippets at 800. A field shortened at that boundary ends with
…[truncated]. This keeps a second search batch from displacing the first
batch from the prompt while preserving the JSON envelope and the cutoff signal.
For larger research loops, use the research_compress pattern to rewrite notes
between searches.
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 and read-only
project inspection are confined to the workspace (--workspace, or the
absolute directory from which the console was launched by default — path-resolved,
no traversal); the selected root is injected into the brain as workspace_root.
The snapshot and every workspace-tool result repeat that absolute root, while
file arguments remain relative and confined to it.
Global and user machines remain available through discovery but do not change this
inspection root. 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:
read-only inspection plus .mkl authoring; 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.