• Overview
  • Functions
  • Definitions
  • Automation Issue Lifecycle
  • Automation Issue Lifecycle - Corner Cases
  • Hints for KI-Writers
  • HIRO 6 - Transition Guide for Users having used HIRO 5 before
  • Transition from KI Syntax 5

Knowledge Item Syntax 6 is a complete re-engineering of the KI language from scratch. It makes use of many concepts of the old syntax but has some significant changes. The following instructions are meant to guide you to understand the differences. The new Knowledge Item Syntax:

  • No longer in XML format

  • Works pretty much the same

  • Easier to write and read

Changes at a glance

  • Elements are called Functions now

  • Blocks are now Sections

  • Scope uses :: instead of : (Example: LOCAL::Var instead of LOCAL:Var)

  • No more explicit var operations, all done with implicit syntax

  • Implied "exists" match unless specified otherwise

  • Specialized "count" operator

Example

Zombie Cleanup KI (Old Syntax)
<KI xmlns="https://graphit.co/schemas/v2/KiSchema" >
    <Title>Zombie Cleanup</Title>
    <Description>only use in case of zombie apocalypse</Description>
    <On>
        <Description/>
        <Var Mode="string" Name="MachineClass" Value="Linux" />
    </On>
    <When>
        <Description/>
        <Var Mode="exists" Name="KillZombieParents" />
    </When>
    <Do>
        <Execute Host="${NODE:NodeName}" RootMode="true" ScriptMode="true">
            <![CDATA[for pid in $(ps -A -ostat,ppid | awk '/[zZ]/{print $2}'); do kill $pid; done]]>
        </Execute>
        <VarDelete Name="KillZombieParents" />
    </Do>
</KI>
New KI Syntax
ki
  title: "Zombie Cleanup"
  description: "only use in case of zombie apocalypse"
on
  MachineClass == "Linux"
  NodeName
when
  KillZombieParents
do
  output: LOCAL::Result = execute("for pid in $(ps -A -ostat,ppid | awk '/[zZ]/{print $2}'); do kill $pid; done", host: "${NodeName}", scriptmode: "true")
  delete(KillZombieParents)

Full list of incompatible changes to old syntax

  1. LOCAL scope is removed from ordering of scopes. That means, if you add something to LOCAL scoping, then it will be the first scope, which will be checked by lookup.

  2. LOCAL scope exists in prepare mode, but is not global as it was (e.g. initializing a LOCAL::COUNTER to 0 in PREPARE and then increasing it in the DO section will only ever increase from 0 to 1)

  3. NodeID, KIID, IID are autoimported and explicit matching in conditions does not work.

  4. It is no longer allowed to use non-existing variables in prepare/do blocks. The same for conditional variables in or statements, which may not exist, can’t be used later.

  5. created_on currently only supports self and any and no more specific origins

  6. KI Syntax does not support value ranges natively

  7. The "eject" command was changed to set the issue in WAITING state, EJECTED state has been removed completely

  8. Sections on and when can’t be empty

  9. Matching in on and when the same variable multiple times works differently now. All matches were evaluated, but only last was saved. Now it has accumulative effect (Foobar > 0 Foobar < 5 before produced anything smaller 5, if there exists something bigger than 5, now it will results, that Foobar is between 0 and 5).

How to use the new syntax

Alias

Aliases are declared by using the as expression. This becomes increasingly important once you want to use the same attribute multiple times with different match sets. Each alias will create a new "value space" while accessing the same variable or alias multiple times will narrow down the previously matched value list.

Variable as VAR1

Example for multi-aliasing the same variable

Variable["Key1"] as VAR1
Variable["Key2"] as VAR2

This will make VAR1 and VAR2 each point to different values of Variable with the respective keys. Without using "as" this would create a never matching condition as matching on "Key1" would create a list that can never contain a "Key2".

Binding

You can either bind to a specific node or to a relative node in a relationship of:

  • @connected

  • @out

  • @in

Use the with …​ end statement to create a more specific context, this will create a separate scope for this match

with
with <ogit type>@<relation:connected/out/in>:<connection type>
# How does the with@related:attribute expression work - https://github.com/arago/civ_hiro/blob/master/kis/a.GameControl/PrintScore.ki#L8

  # this node has a connection ogit/playedby to his parent with a ogit/_type ogit/Freeciv/Game
  with ogit/Freeciv/Game@in:ogit/playedby
    # What should additionally be in this node
    civ/current_turn
  end

Reading Data

Adding data to the execution context of your Issue is as simple as matching a variable or value in a Knowledge Item. Only data that is matched will be added to the context of the Issue and will be available for processing. HIRO does not guess related data, you must teach it to read/match this data if you need it for your use case.

Data Manipulation

You can add, replace and remove data values from variables.

Simple assignment

This should be the default way to reassign or add completely new variables. If used on a variable not previously matched, it will by default replace all values valid for the current scope for that variable with the given value(s). Using @self or @any scoping can be used here as well to replace only local or all values instead. Using the assignment operator on a matched variable will actually remove the previously selected value(s) and add the new value(s) instead.

  • =

Example
A = 1
A = ["a", "b", "c"]
A = B
NODE::A = "f"

Add

Add() can be used to force creation of a new value in an existing variable without overwriting a previously matched value in the process.

  • add()

    • single values - "d"

    • lists - ["a","b","c"]

    • Can assign value from variable in context or new string value

Example
add(A = "d")
add(A = ["a","b","c"])
add(NODE::A = "f")

Delete

With delete() either all values for the given variable from the current scope and context will be deleted or only specific values matching additional conditions.

  • delete()

    • single values - "d"

    • lists - ["a","b","c"]

Example
delete(A)
delete(A["key"])
delete(A, "b")
delete(NODE::A)

The second example will only remove values with a specific key from the variable while the third example will only remove specific values from the variable.

Set

The set() command can be used to overwrite existing variable values which may include values that are not in the current context

  • set()

    • single values - "d"

    • lists - ["a","b","c"]

Example
set(A = "d")
set(A = ["a","b","c"])
set(A["key"] = "e")
set(NODE::A = "f")

Execution

Like in the old syntax the do section will execute the described steps once the conditions of a Knowledge Item are met. While you can use if/else statements to add logic to your Knowledge Items, please be aware that this should be severely limited to checking temporary results of other commands. If possible make if/else decisions in separate KIs on or when blocks. Never ever have if as first command in your do block, this is considered an anti pattern and is an almost certain sign that this KI should be split into two.

  • if

  • else

  • then

  • end

if statements must be closed with and end
Example
if Status == 0 and Output != "" then
  SpotRequestInfo[EC2SpotRequestID] = Output
else
  if ERROR =~ "does not exist" then
    log("spot request not existing anymore, removing")
    delete(NODE::EC2SpotRequestID)
  end
end

Functions

Creating JSON Data in KI

JSON
  LOCAL::JSON = '{
  "op": "call_service",
  "service": "/app_ctrl/stop_app",
  "args": {}
  }
  }'

Using JavaScript in KI

JavaScript
do
  result: NODE::KeysList = javascript("
    result=[];
    for (var i=1; i<= 26;i++){
        result.push(i);
    }
    result;")

Predefined Action Handlers

There are some predefined ActionHandlers that can be used without additional configuration:

  • HTTP

  • local

  • SSH

  • websocket

HTTP
code: LOCAL::CODE,
body: LOCAL::RESULT = action("HTTP", url: "http://localhost/somepath", method: "POST", body: "{\"expr\": \"1 + 1\"}")
local
stdout: LOCAL::STORETO,
stderr: LOCAL::STOREERROR,
exit: LOCAL::STORESTATUS = execute("echo testing execute", host: "_AAE_", timeout: 60)

stdout: LOCAL::STORETO,
stderr: LOCAL::STOREERROR,
result: LOCAL::STORESTATUS = action("ExecuteLocalCommand", command: "echo testing execute", timeout: 60)
SSH
stdout: LOCAL::STORETO,
stderr: LOCAL::STOREERROR,
exit: LOCAL::STORESTATUS = execute("echo testing execute", host: "remote.host.com", timeout: 60)

stdout: LOCAL::STORETO,
stderr: LOCAL::STOREERROR,
result: LOCAL::STORESTATUS = action("ExecuteCommand", command: "echo testing execute", host: "remote.host.com", timeout: 60)
websocket
data: LOCAL::RESULT = action("websocket", url:"ws://${ogit/host}:9090", data: '${LOCAL::JSON}')