Skip to content

10. Calling it from other programs ​

So far a person has read the help and the errors of heat. A workflow manager, a Python script or an AI agent also calls programs, and it has to work out what a program takes, whether a command line is valid, and what to do when a call fails. Text meant for a person makes it guess. This chapter gives it answers it can parse, with the same definitions.

Everything here is opt-in, and a program using none of it behaves as before.

f90
call cli%init(progname='heat', version='v1.0', description='Solve the 2D heat equation on a square plate', &
              schema_option=.true., check_option=.true., dry_run_option=.true., error_format_env='HEAT_ERROR_FORMAT')
f90
call cli%add(switch='--write-skill', help='Write the agent skill and stop', required=.false., def='.false.', act='store_true')
call cli%add_group(group='run', description='Run a simulation')
call cli%add(group='run', switch='--steps', help='Time steps', required=.true., val_type='I4P', min='1')
call cli%add(group='run', switch='--cfl', help='CFL number', required=.false., def='0.25', val_type='R8P', min='0', max='1')
call cli%add(group='run', switch='--label', help='Label of the run', required=.false., def='run')
call cli%add(group='run', switch='--token', help='Access token', required=.false., def='', sensitive=.true.)

Say what a value is ​

val_type declares the type of a value, so that parse can check it before the program starts, from any source. Without it FLAP only knows a value when get reads it, possibly hours into a run. --steps and --cfl declare theirs; --label is free text.

Describe the program ​

schema_option=.true. adds --schema, a JSON description of the program. A caller reads this once and knows every option, type, range and default. heat run --schema describes one command, so a caller need not load the whole program:

$ heat run --schema | head -n 12
{
  "schema_version": 1,
  "program": {"name": "heat", "version": "v1.0"},
  "command":
  {"name": "run", "aliases": [], "description": "Run a simulation", "exclusive_with": [],
    "options": [
      {"switch": "--steps", "help": "Time steps", "required": true, "action": "store", "value": {"type": "integer", "minimum": 1, "maximum": 2147483647, "x-flap-kind": "I4P"}, "typed": true},
      {"switch": "--cfl", "help": "CFL number", "required": false, "action": "store", "value": {"type": "number", "minimum": 0, "maximum": 1, "x-flap-kind": "R8P"}, "typed": true, "default": "0.25"},
      {"switch": "--label", "help": "Label of the run", "required": false, "action": "store", "value": {"type": "string"}, "typed": false, "default": "run"},
      {"switch": "--token", "help": "Access token", "required": false, "action": "store", "value": {"type": "string"}, "typed": false, "sensitive": true}
    ],
    "positionals": [],

Check before running ​

check_option=.true. adds --check, before or after the command. It runs every stage of parse and stops:

$ heat --check run --steps 100 --cfl 0.4 --label demo
heat: command line valid
2 options not type-checked: run --label run --token

It names what it could not validate: here --label and --token, which have no declared type. A wrong value is reported with no run at all:

$ heat --check run --steps 100 --cfl 7
heat: error: value "7" of "--cfl" is out of range [0, 1]!

Try 'heat run --help' for help.
[exit status 2]

Errors a program can read ​

error_format_env='HEAT_ERROR_FORMAT' lets the caller ask for JSON errors, one line each, without colours or usage. The line has the code, what to change (class), the option at fault and FLAP's suggestions:

$ HEAT_ERROR_FORMAT=json heat run --stepz 100
{"schema_version":1,"level":"error","code":15,"class":"usage","message":"heat: error: switch \"--stepz\" is unknown! Did you mean \"--steps\"?","argument":"--stepz","suggestions":["--steps"]}
[exit status 2]

Exit statuses that say what to do ​

End the program with cli%exit_status():

f90
call cli%parse(error=error)
if (error /= 0) stop cli%exit_status(), quiet=.true.
$ heat run --steps many
heat: error: cannot convert "many" of option "--steps" to an integer!

Try 'heat run --help' for help.
[exit status 2]

Status 2 tells the caller to fix the command line and retry; 3, to fix a file or a variable; 4, not to retry at all, because the program itself is wrong. Your own failures keep status 1. The whole table is in Error Codes.

Try it without doing it ​

dry_run_option=.true. adds --dry-run, but only your program can honour it:

f90
if (cli%is_dry_run()) then
  print '(A,I0,A)', 'would run ', steps, ' steps'
  stop
endif
$ heat --dry-run run --steps 100
would run 100 steps

A promise FLAP cannot keep for you

A program that enables --dry-run and forgets to read it performs the real action while its caller believes it did not. Enable the option only once every side effect is guarded.

Record what ran ​

save_provenance writes a manifest of the run: the program, the command line, and each value with its source. It has no date, host or directory, so the same inputs give the same bytes. sensitive=.true. keeps --token out of it, as ***, and out of error messages:

f90
call cli%save_provenance('heat.manifest.json', error=error)
$ heat run --steps 100 --token s3cret && cat heat.manifest.json
ran 100 steps
{
  "schema_version": 1,
  "program": {"name": "heat", "version": "v1.0"},
  "arguments": ["run","--steps","100","--token","***"],
  "status": 0,
  "dry_run": false,
  "values": [
    {"command": "", "name": "--write-skill", "value": ".false.", "source": "default"},
    {"command": "run", "name": "--steps", "value": "100", "source": "command line"},
    {"command": "run", "name": "--cfl", "value": "0.25", "source": "default"},
    {"command": "run", "name": "--label", "value": "run", "source": "default"},
    {"command": "run", "name": "--token", "value": "***", "source": "command line"}
  ]
}

Introduce it to an agent ​

save_skill writes a SKILL.md, the Agent Skills format, from the same definitions, so it cannot fall out of date as long as you regenerate it at build time:

f90
call execute_command_line('mkdir -p heat')
call cli%save_skill('heat/SKILL.md', error=error)
$ heat --write-skill && cat heat/SKILL.md
---
name: heat
description: "Solve the 2D heat equation on a square plate. Use when running heat."
---

# heat

## Options

- `--write-skill`: Write the agent skill and stop

## Commands

Read the options of a command with `heat COMMAND --schema`.

- `run`: Run a simulation

## Calling it

- `--schema` prints the full description as JSON.
- `--check` validates a command line without running it, and names what it could not validate.
- `--dry-run` reports what would be done, without doing it.
- `HEAT_ERROR_FORMAT=json` writes errors as JSON lines.
- Exit status: 0 success; 1 failure of the program; 2 fix the command line and retry; 3 fix a file or a variable; 4 the program is wrong, do not retry.

A good --help already serves an agent that has been told about the program (the measurement). The skill is how an agent learns that the program exists.

In a script ​

A caller needs no FLAP code. flap_tool.py, about 70 lines of Python, turns --schema into the tool description an LLM API expects, and checks each call with --check.

What you learned

val_type, --schema, --check, JSON errors, exit_status, --dry-run, save_provenance, sensitive, save_skill. Reference: Machine-readable Interfaces.

That is the whole tour. For quick answers, see the cookbook.

Released under GPL v3, BSD 2-Clause, BSD 3-Clause or MIT, at your choice.