ARMANOS

Automation builder

No-code automation

Assemble a flow out of steps, then run it on many profiles at once. Every step runs inside a profile's own browser, with that profile's fingerprint, cookies and proxy.
16
Step kinds
4 levels
Nesting depth
up to 8 per run
Profiles at once
100 per profile per run
Screenshots

A flow is an ordered list of steps

A flow has a name, an ordered list of steps, its own variables, the profiles it targets, and how many of them to drive at once. All of that belongs to the flow itself. Switching between flows never carries one flow's profile selection into another, and the selection survives a restart.

Flows live on your computer, in rpa-flows.json inside the app data folder. The file is written with owner-only permissions, because steps often carry a login or a form value. Every save passes through a sanitizer that keeps the known fields and drops everything else, so the code that runs the flow can trust the shape it reads back.

The ceilings are deliberate: 200 flows per file, 500 steps per list, and container steps nested at most 4 levels deep. They exist so a hand-edited or imported file cannot make the app recurse without end.

Sixteen kinds of step exist today. Ten are the everyday ones. Six are the advanced ones: branching, list iteration, tabs, cookies, your own JavaScript and network waits.

StepWhat it doesWhat it needs
Go to URLLoads an address in the current tabAn http or https address, or a variable that expands into one
WaitPauses for a set time, or until an element appearsMilliseconds, or a CSS selector and a timeout
ClickScrolls the element into view, then presses its centreA CSS selector
Input textFocuses a field, clears it, types the valueA selector and a value
ScrollScrolls by a number of pixels, or to an elementAn amount, or a selector
Extract textReads an element's text into a variableA selector and a variable name
Take screenshotSaves a PNG of the pageNothing
Press keySends one key press to the pageA key name, Enter by default
Random delayWaits a random time between two boundsTwo values in milliseconds
RepeatRuns its body a fixed number of times1 to 1000 passes
If / elseTests the page or a variable, runs one of two branchesOne of five tests
For each elementRuns its body once per matched elementA selector, a cap, a variable
Tab controlOpens, switches or closes a tab of the same profileAn address, an index or a URL fragment
CookiesReads, writes, deletes or clears cookies in this profile's sessionA name, plus an open page or a domain
Run JavaScriptRuns your code in the page and stores the resultCode, up to 20000 characters
Wait for requestWaits until the page makes a request whose URL contains a fragmentA fragment and a timeout
The automation builder: the flow list on the left, the step list with an indented repeat body, variables, target profile chips and the run log.
One screen: flows, steps, variables, target profiles and the live run log.

You build the flow by hand, and the builder names what is broken

There is no recorder. ARMANOS does not watch you click and turn that into steps. You add each step from a form: choose the kind, fill the fields it needs, save. Steps move up and down with two buttons, and deleting a container tells you first how many nested steps go with it.

What the builder gives you instead is a check that runs while you edit. It looks for the silent breakages, the ones that only show up mid run on a live account: a click with no target, a wait with neither a time nor an element, an extract with nowhere to put the result, a tab step with no address, a JavaScript step with no code.

Every remark says which step number it is about. Errors are marked apart from warnings, and the remarks sit right under the step list.

The check never blocks saving. An unfinished flow is a normal working state, you come back to it tomorrow, and refusing to save would only lose your work.

How a flow leaves this computer

Export writes one JSON file, and it leaves out typed values, cookie values and variable values on purpose: that is where logins live. Variable names stay, so whoever receives the file can see what to fill in, and {{variable}} references stay too, or the flow would not assemble. Import always creates a new flow, never overwrites one, and it reports how many steps the sanitizer dropped instead of losing them quietly. To move a flow between your own machines with values intact, use the shared library in your account.

Variables put a different value into every profile

Any text field of a step can carry {{name}}. At run time it is replaced from the flow's variable map, and an unknown name becomes an empty string rather than an error.

The map is built once per profile, in three layers: the flow's default values, then the built-in profile fields, then the per-profile overrides you typed into the matrix under the target chips. That is how one flow signs into twenty accounts without twenty copies of the flow.

Variable names may be written in any script. The app ships in 22 languages, and a name like gorod, 都市 or مدينة survives a save and substitutes at run time. Both halves had to be fixed together: a name that saves but does not substitute makes a flow look correct and quietly do nothing.

Reading fills variables too. Extract text, the cookie read and the JavaScript step all write their result into the name you give, up to 4000 characters. A value that came from a variable is written to the run log as dots, because that log lands on disk and is shown on screen.

  • profile.name

    The profile's name, as it appears in your list.

  • profile.group

    The group the profile belongs to.

  • profile.totp

    A one-time code from the profile's own secret. It is computed at the moment of substitution, not when the run starts, because a code lives thirty seconds and a run is longer than that.

Repeat, if / else and for each

Repeat runs its body a fixed number of passes, from 1 to 1000. The loop itself is not counted as work, its children are, so progress reflects what actually happens.

If / else offers five tests: an element exists, an element is missing, an element's text contains something, a variable equals something, the URL contains something. Only the winning branch runs. Which branch wins is not knowable in advance, so the run's total grows the moment it is decided, and the progress bar lands on the end instead of stalling short.

For each takes a selector and a cap, ten by default. Each pass marks the current element in the page with an attribute carrying the loop depth, so nested loops never overwrite each other. Inside the body, @item points at that element, and @item .title works as well. The list is re-queried on every pass, so a page that re-renders between passes still works, and if the list shrinks the loop ends early instead of failing.

Outside a for each, @item deliberately resolves to a selector that matches nothing. Quietly falling back to the whole document would make a misplaced step click something you never chose. At the deepest nesting level the builder stops offering container steps, so it cannot produce a body the store would drop on save.

One flow, many profiles, and a queue that waits

Target profiles are chips under the step list, with select all and clear. Ids of profiles you deleted are pruned when the card is drawn, so Run is never enabled against a profile that no longer exists.

Concurrency is 1, 2, 3, 5 or 8. The run starts that many workers, but never more than your machine's open-profile limit, which is derived from memory: total RAM minus 3 GB for the system and the app, then one browser per gigabyte, floor 4, ceiling 32. You can override that number by hand if you know your hardware.

When a worker meets the limit, the profile is not written off. The worker waits for a free slot, checking every five seconds for up to thirty minutes, and only then reports the failure. Before this, a night warm-up of thirty profiles could collapse in a fraction of a second, because the first worker to hit the ceiling marked its profile failed and immediately took the next one.

Each profile is launched if it is not already open, then the steps run against its first window. You can walk away to another flow: the run keeps going, and a banner with Stop follows you. Stop is cooperative, checked before every step at every nesting level, so a loop unwinds instead of being killed mid action.

Concurrency choices
1, 2, 3, 5, 8
Workers actually started
never more than the open-profile limit
Open-profile limit
(RAM minus 3 GB) per gigabyte, floor 4, ceiling 32
Waiting for a slot
polled every 5 s, up to 30 min
Run log
600 lines kept, last 400 sent to the window
Finished runs on disk
last 20

Numbers taken from the run engine and the memory-based limit in the desktop app.

When a step fails

A failed step does not end the run. The step is logged with its error, a counter for that profile goes up, and the next step is tried. A flow of thirty steps tells you which ones broke, instead of stopping at the first one and hiding the rest.

Errors the engine names get a translated label: no element, wait timeout, navigation failed, request timeout, no cookie, no tab, too many screenshots. Anything else, including a message from the browser itself, is shown exactly as it came back, never swallowed and never rewritten.

The outcome is honest. Done means the written steps ran. If any step of a profile failed, that profile is marked failed and says how many. If any profile failed, the run is failed, and the Task Center notification is red rather than green. Earlier this was judged only by whether the profile itself crashed, so a flow of three steps where all three broke reported success.

That count comes from a counter kept beside the run, not from the log, because the log is trimmed to the last 600 lines. On a long run the early failures were pushed out before you could read them, and the run turned green again exactly where it is long and unattended.

Finished runs are written to rpa-runs.json, the last twenty of them. A run that happened overnight can still be read in the morning, after a restart, which is the only moment a run log really matters. The JavaScript step carries a sixty second limit for the same reason: a promise in page code that never settles used to hang the run forever, and Stop did not help.

Screenshots and where they land

The screenshot step captures the page and writes a PNG. On the built-in engine that is the window's own capture; on ARMANOS Browser it is Page.captureScreenshot over the debugging channel. One step, one code path, two engines.

Files go to the rpa-screenshots folder inside the app data folder. The folder is created with owner-only permissions and every PNG is written the same way. A screenshot of a profile is an open session: mail, messages, and a one-time code if one happens to be on screen.

There is a ceiling of 100 shots per profile per run. A screenshot step inside a repeat inside a repeat can otherwise fill a disk overnight. Passing it raises too many screenshots as an ordinary step error, so you see it in the log rather than discovering it by running out of space.

Open screenshots folder sits in the run card header and on the empty state, so the PNGs stay reachable after a restart, when no run is loaded on screen.

The debugging domains we do not turn on

ARMANOS Browser is a separate Chromium process, 153.0.7978.0, so a flow drives it through the DevTools protocol on loopback. The built-in Electron engine is driven directly through its own window handle. Both are wrapped in one shape, so each step is written once and the fork is a first-class automation target rather than a refusal.

Two domains are never enabled: Runtime.enable and Page.enable. Enabling them makes Chromium install hooks that page JavaScript can observe, which is what the console.debug and Error.stack timing probes look for.

Nothing here needs them. Runtime.evaluate, Page.navigate, Input events and Network commands all work without enabling their domain. Page readiness is polled with document.readyState instead of subscribing to lifecycle events. Network.enable is switched on only while a wait-for-request step is pending, and it is browser side, leaving no JavaScript-visible trace.

The WebSocket handshake also sends no Origin header, because sending one makes Chromium demand an extra command-line flag, and the command line is visible. On the built-in engine, the unprotected debugging port is opened only when that engine is the one running: any program on the same machine under the same user could otherwise reach it.

On a mobile profile, the click step sends touch events instead of mouse events. Measured on a live window: the engine turns mouse presses into touches, the call never returns, and every flow on a mobile profile used to stall on its first click and fail on a timeout.

What this buys you, and what it does not

Leaving those domains off removes one signal a page can read directly. It says nothing about behaviour. Speed, order, volume and the hour you run at are still yours to shape, and a flow that clicks thirty identical things in thirty seconds looks like a flow that clicks thirty identical things in thirty seconds.

Scheduling a flow

A schedule belongs to a flow and names the profiles, the time and how many profiles run at once, up to five. Three modes: daily at a time, weekly on chosen days, or every N hours.

Times are local. After a clock change, nine in the morning is still nine in the morning, not eight.

Missed runs are not caught up. If the computer slept for three days, three runs did not happen, and firing them all at once is exactly the burst you want to avoid. The number of missed runs is recorded, so the window can tell you about it without acting on it. A profile you have open right now is left alone.

A schedule works only while ARMANOS is open. There is no system service, the window says so plainly, and we would rather say it than let you find out on a Monday.

The outcome is written back when the run ends, not when it starts, and it is stored as a token: ok, stopped, error:2/5. Words are chosen at display time, so a schedule that travels to a machine set to another language still reads correctly. All schedules are also listed in one window, ordered by what happens soonest.

Schedules arriving from your account land switched off on this machine on purpose: two open copies of the app would otherwise run one flow twice. There is a button to switch it on here, in that same list.

  1. 1

    Pick the flow

    Open the clock button on its row in the flow list.

  2. 2

    Pick when

    Daily, weekly on chosen days, or every N hours, in your local time.

  3. 3

    Pick who

    Tick the profiles, then choose how many run at once, 1 to 5.

  4. 4

    Leave the app open

    The schedule fires from the running app. The next due time is shown on the flow row.

  5. 5

    Read the outcome

    The row shows the last run, its result, and how many runs were missed.

What this does not do

  • There is no recorder. Nothing watches your clicks and turns them into steps. Every step is added by hand from a form.
  • A flow is a script, not a person. It repeats what you wrote, at the pauses you set, fixed or random. It does not read a page the way you do, and a site that changes its layout breaks a selector.
  • No captcha solving, no image recognition, no machine learning step. If a site asks a human question, the flow stops at it.
  • The scheduler runs only while the app is open. There is no background service, a closed laptop misses its runs, and missed runs are never replayed in a batch.
  • Keeping the detectable debugging domains off removes one signal. It is not a claim about detection in general, and nothing here promises that a site will treat your run as a person.

How to check

Each claim, the file that implements it, and the stand that guards it.

The go to step accepts only http and https, and the address is checked again at run time, after variables expand
apps/desktop/src/lib/rpa.js · apps/desktop/test/rpa-reach.js
The builder names what is broken before a run, and never blocks saving
apps/desktop/src/lib/flowCheck.js · apps/desktop/test/flow-check.js
Done means the steps ran: a profile whose steps failed is marked failed, counted from a counter and not from the trimmed log
apps/desktop/src/main/main.js · apps/desktop/test/run-result-honest.js
A run that meets the open-profile limit waits for a slot instead of writing the profile off as failed
apps/desktop/src/main/main.js · apps/desktop/test/сценарий-ждёт-места.js
A flow really runs on ARMANOS Browser over the debugging channel, with Runtime.enable and Page.enable never called
apps/desktop/src/lib/cdp.js · apps/desktop/src/lib/forkPage.js · apps/desktop/test/сценарий-живьём.js

Build one flow, run it on ten profiles

The builder, the run log and the scheduler are in the app itself. Nothing about a flow leaves your computer unless you export it or sync it to your account.