Analytica Command Line/Automation

Release:

 • 4.6 •  5.0 •  5.1 •  5.2 •  5.3 •  5.4 •   •  6.0 •  6.1 •  6.2 •  6.3 •  6.4 •  6.5 •  6.6 •  7.0 •  7.1 •  7.2

New in Analytica 7.2. See also the parent page, Analytica Command Line.

Why automation mode exists

A dialog box is fine when a person is sitting in front of Analytica. It is fatal when nobody is. A single unexpected "Save changes?" or "This variable failed its Check" dialog will stop an overnight benchmark, a QA sweep, or an AI agent's session dead, and you often will not find out until much later that nothing after that point ran.

Launching with /Automation answers those dialogs for you:

Analytica.exe /Automation MyModel.ana

Every modal dialog that would block gets an answer — either a sensible built-in default, or an answer computed by a handler function you register with RegisterAutomationModalHandler. The run keeps going.

Three things are worth being clear about up front:

  • It is not a headless mode. The main window, diagrams, result windows, HTML dialogs, and Assista all work exactly as usual. You can watch the run happen, and you can take over with the mouse at any point. If you want Analytica without a user interface, that is a different thing entirely.
  • It changes nothing unless you ask for it. Without the flag, every dialog behaves exactly as it always has. Automation mode is a property of the process, set once from the command line.
  • The defaults are chosen to protect the model under test, not to make progress at all costs. Where "keep going" and "do not touch anything" conflict, automation picks "do not touch anything" — see #What the built-in defaults do.

Getting started

Just run it

Start with no handler at all. The built-in defaults handle a surprising amount on their own:

Analytica.exe /Automation /eval:Run_batch "Claim analysis.ana"

If your model already runs unattended except for the occasional dialog, this may be all you need.

See which dialogs your scenario actually poses

Before writing any handler, find out what you are dealing with. Add a trace file:

Analytica.exe /Automation /AutomationTrace:C:\Temp\run1.jsonl MyModel.ana

Every dialog is appended to run1.jsonl as one JSON object per line, recording what it said and how it was answered. See #The trace log.

You can also run the trace without /Automation:

Analytica.exe /AutomationTrace:C:\Temp\baseline.jsonl MyModel.ana

This is observe mode. Dialogs appear and block normally, exactly as for any other user, and the log records what you chose. It is the easiest way to walk a scenario by hand once and get a complete inventory of its dialogs, along with the answers a human considered correct — which is a good starting specification for the handler you are about to write.

What the built-in defaults do

With no handler registered, dialogs are answered as follows.

Dialog Automatic answer Why
Message boxes (MsgBox and internal ones) The default button — the one Enter would press Matches what a user pressing through quickly would get
AskMsgText The «default» text The value the model already proposed
AskMsgNumber The «default» number
AskMsgChoice The «default» option
Save changes? on close or quit No — discard An unattended run must never overwrite the model it is testing
Evaluation warnings (Check failures, bad values) Ignore this one See the note below
"Stop evaluating and edit the definition?" No The error is already recorded; opening an editor mid-run helps nobody
File open / file save prompts Fail, as if cancelled Guessing a filename is worse than failing. A handler can supply a real path
Other template-based dialogs Cancel, without running the dialog Cancelling a dialog that never opened has no side effects
Externally-changed linked module Later (keep what is in memory)
Legal agreement, license prompts Accepted So a fresh machine does not hang at startup
Unrecognized command-line option Continue Never exit(-1) in the middle of a batch

Two of these deserve emphasis.

Save changes is answered No. This is deliberately the opposite of what Analytica's existing headless/server path does, which silently saves. A benchmark that mutates its fixture model between runs is not measuring what you think it is measuring, and a QA run that rewrites the model under test destroys the evidence. If your automation genuinely wants to save, make that explicit — have your handler answer 'Yes' to the 'SaveChanges' dialog, so the intent is visible in the trace.

Warnings are ignored once, not switched off. Pressing "Ignore Warnings" interactively silences warnings for the rest of the session. Automation deliberately does not do that: each warning is cleared individually, so the tenth warning is still reported to your handler and still written to the trace. You get a complete record instead of a log that goes quiet after the first problem.

Dialogs raised on a worker thread (during parallel evaluation, for instance) always take the built-in default and are recorded with "source":"offthread-default". Your handler is only consulted on the main thread.

Writing an automation handler

When the defaults are not enough — when the answer depends on which dialog it is, or when you want to record something, or ask a person or an orchestrator — register a handler:

RegisterAutomationModalHandler( handler )

The handler is any callable: a handle to a UDF, a local (lambda) function, or a Python callable. It takes one parameter, receives a struct describing the dialog, and returns the answer. It may be registered at any time, but it is only consulted when the process is running with /Automation, so it is harmless to leave the registration in a model that people also use interactively.

The simplest useful handler answers nothing and just watches:

Function Log_dialogs( s : atom ) :=
    ( WriteTextFile( 'dialogs.log',
                     s->seq & '  ' & s->kind & '  ' & s->caption & Chr(13) & Chr(10),
                     append: True, warn: False );
      Null )              { Null means: use the built-in default }
RegisterAutomationModalHandler( Handle(Log_dialogs) )

What the handler receives

The parameter is an AutomationDialogStruct with these fields, read with the -> operator:

Field Meaning
kind The family of dialog: 'MsgBox', 'Ask', 'AskText', 'AskNumber', 'AskChoice', 'FileOpen', 'FileSave', 'Warning', 'Redefine', 'SaveChanges', 'LinkedModuleChanged', 'Win32Dialog', 'MacAppDialog', 'Alert', 'Comment', 'CmdLineWarning'
id A stable identity for the specific dialog when one is known, such as 'SaveQuitting' or 'CheckFailure', or the internal name of a template dialog. Null otherwise
caption The title bar text
body The message text
buttons A list of the button names offered, such as ['Yes','No','Cancel']. Null when the dialog is not a button dialog
defaultAnswer What the built-in default would answer, as text. Useful for logging, and for "default unless I say otherwise" logic
obj A Handle to the object the dialog is about, when there is one. Null otherwise
att The attribute involved, as text, when relevant
seq The sequence number of this dialog, matching the seq in the trace log

What the handler returns

Return Effect
Null Use the built-in default. This is the right answer most of the time
Text For a button dialog, the name of the button to press: 'Yes', 'No', 'OK', 'Cancel', 'Ignore', 'Retry', 'Abort'. For 'AskText', the text to enter. For 'FileOpen' or 'FileSave', the full path of the file to use
Number For 'AskNumber', the value. For 'AskChoice', the 1-based index of the option to choose

Answering a file dialog with a path is often the single highest-value thing a handler does: instead of the prompt failing, the run proceeds with exactly the file your driver wants, chosen at the moment it is asked for.

A handler that returns Null for everything behaves exactly like no handler at all, which is how you stand a handler down without unregistering it.

A worked example

Function Bench_handler( s : atom ) :=
  Var body := If IsNull(s->body) Then '' Else s->body;
  ( { 1. Record everything, whatever we decide below. }
    WriteTextFile( 'bench-dialogs.log',
                   s->seq & Chr(9) & s->kind & Chr(9) & s->defaultAnswer & Chr(9) & body & Chr(13) & Chr(10),
                   append: True, warn: False );

    { 2. Feed the scenario the input file it is about to ask for. }
    If s->kind = 'FileOpen' Then Bench_input_path

    { 3. This scenario is supposed to reach the end; a Check failure means it did not. }
    Else If s->kind = 'Warning' Or s->id = 'CheckFailure' Then
       ( WriteTextFile( 'bench-failures.log', 'FAILED: ' & body & Chr(13) & Chr(10),
                        append: True, warn: False );
         Null )

    { 4. Everything else: whatever the built-in default is. }
    Else Null )

Rules the handler runs under

  • Main thread only. Dialogs raised on worker threads take the built-in default (traced as offthread-default).
  • No re-entry. If your handler itself causes a dialog — and a WriteTextFile easily can — that inner dialog takes the built-in default rather than calling your handler again. It is recorded as reentrant-default, so you can see it happened.
  • Errors are contained. If your handler raises an error, the built-in default is used, the trace records handler-error-fallback, and the run continues. A broken handler degrades to the defaults; it does not stop the run and never pops up a dialog of its own about the failure.
  • It cannot dirty the model. The handler runs with model-dirtying and autosave suppressed, so merely observing a dialog cannot mark the model changed. Note this means a handler is not the place to modify the model.
  • The error state is preserved. Most of these dialogs occur while an error or warning is pending. That state is saved before your handler runs and restored afterwards, so your handler sees a clean slate and does not disturb the error being reported.
  • It is timed. The wall-clock time your handler takes is recorded in the trace as handlerMs, so you can see how much your instrumentation perturbs a benchmark.

The trace log

/AutomationTrace:filename writes JSON Lines — one self-contained JSON object per line, appended, each written in a single operation so that concurrent processes never interleave a record.

A run starts with:

{"ev":"start","pid":66668,"exe":"...\\Analytica.exe","ver":"7.2.0.318","cmdLine":"...","automation":true,"t":"2026-08-07T11:29:47.694"}

and then one record per dialog:

{"ev":"dialog","seq":5,"t":"2026-08-07T11:29:48.108","thread":80872,"kind":"MsgBox",
 "caption":"Overwrite?","body":"...","buttons":["Yes","No"],
 "response":"No","source":"handler","handlerMs":0.939}

The field to look at first is source, which says where the answer came from:

source Meaning
default The built-in default answered
handler Your registered handler answered
handler-error-fallback Your handler raised an error; the default was used and the run continued
reentrant-default The dialog was raised while your handler was running, so the default was used
offthread-default The dialog was raised off the main thread, so the handler was not consulted
shown Observe mode: the dialog was actually displayed and this is what the user chose

Registering a handler also writes a {"ev":"handlerInstalled", ...} record, so you can confirm from the log alone that your library really did get loaded and registered.

Body text is truncated at 4 KB, with "bodyTruncated":true added when that happens.

Because the file is appended rather than truncated, a driver can create it, start tailing it, and then launch Analytica — no polling for the file to appear, and no race at startup.

Reading the trace back in Analytica

The log is ordinary text, so a model can read its own trace:

Var lines := SplitText( ReadTextFile('run1.jsonl'), Chr(10) );
ParseJSON( lines[@Line = 2] )

This is handy for a QA model that runs scenarios and then asserts on which dialogs appeared.

Putting it in a library

You will usually want the handler in a library rather than in the model under test, so that the model stays exactly as shipped and the automation is bolted on from outside. Load it with /lib::

Analytica.exe /lib:BenchmarkDriver.ana /Automation /AutomationTrace:C:\Temp\run1.jsonl MyModel.ana

Libraries named with /lib: load before the model, into SysLib_Customizations, and stay loaded as models are closed and opened. So a driver library can register its handler once at startup and keep answering dialogs across many models in one session. See the /lib: entry on Analytica Command Line.

To have the library register itself as it loads, put the RegisterAutomationModalHandler call in a button in the library and mark that button to evaluate proactively, or call it from the driver's entry point before it opens the first model.

Uses

A benchmark driver

Everything that makes benchmarks unreliable — a stray dialog stalling one run, a fixture model quietly rewritten between runs, a scenario that silently stops halfway — is what this mode is for.

  • Launch each run with /Automation and a per-run /AutomationTrace: file.
  • Let the built-in Save-changes default protect your fixture models. Verify it by checking that the .ana file's timestamp is unchanged after the run.
  • Have the handler answer FileOpen with the scenario's input file, so the same model can be run against many datasets without editing it.
  • Treat the trace as part of the result. A run whose trace contains a Warning or CheckFailure record did not really succeed, even if it finished.
  • Watch handlerMs to confirm your instrumentation is not itself distorting the timings.

An AI agent, such as Claude Code

An agent driving Analytica has the same problem as a benchmark, only worse: it cannot see a modal dialog, and a blocked Analytica just looks like one that stopped responding.

Combine automation mode with the MCP server in Analytica:

Analytica.exe /Automation /mcp:6541 /AutomationTrace:C:\Temp\agent.jsonl MyModel.ana

Now the agent's tool calls cannot deadlock behind a dialog. Some things worth doing in the handler in this setup:

  • Make dialogs visible to the agent. Write each dialog to a file, or into a model variable, that you expose through an {@mcpTool} UDF. The agent can then ask "what did Analytica ask me while that ran?" and get a real answer instead of guessing why a call returned nothing.
  • Answer according to what the agent is trying to do. A handler can read a model variable that the agent set through another tool, and use it to decide — for instance, permitting a save only while an "allow writes" flag is on.
  • Ask the orchestrator. RunConsoleProcess with block: True lets the handler call out to a broker process and wait for its answer, so a genuinely ambiguous dialog can be escalated to the agent, or to a person, instead of being defaulted. Keep the timeout on the broker's side.
  • Talk to a browser-based driver. If the agent is already attached through /remote-debugging-port:, the handler can call ExecuteJavaScript on a CefWindow object to signal what just happened. This is fire-and-forget and does not block the run.

Automated QA

  • Drive a scenario with /eval: to press a button, and let automation answer whatever the button's script raises.
  • Use observe mode first to capture what the dialogs should be, then assert against that inventory in later runs. A new record appearing in the trace, or an expected one disappearing, is exactly the kind of regression that is otherwise easy to miss.
  • Have the handler answer the specific dialog the test is about with a specific button, and leave everything else on the defaults. That keeps each test's intent obvious.

Limitations

  • Not every dialog in Analytica is intercepted yet. A number of older dialogs, mostly in OLE linking and a few utility dialogs, are still shown and still block. If you hit one, the trace will show nothing at that point while the process sits there — comparing the trace against a run that finished is the quickest way to spot it. Please report any you find.
  • The handler is only consulted on the main thread. Dialogs from worker threads take the defaults.
  • A handler cannot modify the model; it runs with dirtying suppressed.
  • Automation mode applies to desktop Analytica. The server (ACP) already answers dialogs through its own web mechanism, and ignores /Automation. The /AutomationTrace: log does work in either.

See Also

Comments


You are not allowed to post comments.