RPA Tasks, Long-Running Automation, and Human-in-the-Loop Workflows: Guided Hands-On Workflow
Build and resume a disposable local Robot Framework task workflow with synthetic inbox/outbox files, durable checkpoints, approval records, deterministic idempotency, and interruption evidence.
Learning objectives
- Build a disposable inbox/outbox workflow whose durable state survives separate Robot processes.
- Make each work item idempotent using stable IDs, deterministic outputs, and completion markers.
- Simulate a human approval with an explicit record and bounded wait rather than an unattended dialog.
- Inject an interruption after one durable checkpoint, preserve evidence, and resume without duplicate effects.
- Separate workflow cleanup from ordinary task teardown and prove deletion stays inside the owned temporary root.
Current compatibility baseline — verified 2026-08-31.
Robot Framework 7.4.2 is the stable course baseline
and requires Python 3.8+. Task files use *** Tasks ***;
task execution uses the same engine as tests, while generic
automation/RPA mode changes terminology and enables task-oriented
settings and selectors such as Task Setup,
Task Teardown, Task Timeout,
--task, and --rpa. The standard
Dialogs 7.4.2 library is interactive and blocks
while waiting for a desktop user, so it is optional only. The
mandatory learning path is fully local and uses Robot Framework core
plus OperatingSystem, String, DateTime, Collections, and BuiltIn.
The external RPA Framework 33.0.1 release
(2026-08-23; Python 3.10–3.13) is mentioned only as an optional
ecosystem layer; no commercial Control Room, cloud orchestrator,
email account, finance system, browser, database, SSH service,
Pabot, container runtime, CI provider, or production target is
required.
1. Scenario: two synthetic work items, one durable root
rf19-rpa-lab/
├── resources/
│ └── workflow.resource
├── tasks/
│ ├── workflow.robot
│ └── cleanup.robot
└── evidence/
├── first/
├── resume/
├── idempotent/
└── cleanup/
system temp / rf19-rpa-demo-001/
├── .owner
├── inbox/
├── outbox/
├── approvals/
└── state/
├── pending/
├── approved/
├── completed/
└── ledger.txt
The repository contains only automation source and result
directories. Durable work state is intentionally outside the source
tree, under the system temporary directory. The stable
RUN_ID is both an ownership key and the path component
used by separate Robot processes to reopen the same workflow.
2. Preflight and installation
python -m venv .venv
# Activate .venv using your shell's normal command.
python -m pip install "robotframework==7.4.2"
python --version
python -m robot --version
python -c "import tempfile; print('durable lab parent:', tempfile.gettempdir())"
# Optional only; not needed for the mandatory workflow:
# python -m pip install "rpaframework==33.0.1"
Do not install Dialogs or RPA Framework just to complete the lab: Dialogs is already a Robot standard library, and neither it nor external RPA packages are necessary for a file-based approval/checkpoint exercise.
3. Domain workflow resource: ownership, checkpoints, and idempotency
*** Settings ***
Library OperatingSystem
Library String
Library DateTime
Library Collections
*** Variables ***
${RUN_ID} demo-001
${INTERRUPT_AFTER} NONE
${ROOT_PREFIX} rf19-rpa-
@{WORK_ITEMS} WI-001 WI-002
*** Keywords ***
Resolve Workflow Paths
${root}= Normalize Path ${TEMPDIR}${/}${ROOT_PREFIX}${RUN_ID}
VAR ${WORK_ROOT} ${root} scope=SUITE
VAR ${INBOX_DIR} ${WORK_ROOT}${/}inbox scope=SUITE
VAR ${OUTBOX_DIR} ${WORK_ROOT}${/}outbox scope=SUITE
VAR ${APPROVAL_DIR} ${WORK_ROOT}${/}approvals scope=SUITE
VAR ${STATE_DIR} ${WORK_ROOT}${/}state scope=SUITE
VAR ${PENDING_DIR} ${STATE_DIR}${/}pending scope=SUITE
VAR ${APPROVED_DIR} ${STATE_DIR}${/}approved scope=SUITE
VAR ${COMPLETED_DIR} ${STATE_DIR}${/}completed scope=SUITE
VAR ${LEDGER_DIR} ${STATE_DIR}${/}ledger scope=SUITE
VAR ${OWNER_FILE} ${WORK_ROOT}${/}.owner scope=SUITE
Validate Run Identity And Root
Should Match Regexp ${RUN_ID} ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
${actual}= Normalize Path ${WORK_ROOT} case_normalize=${True}
${expected}= Normalize Path ${TEMPDIR}${/}${ROOT_PREFIX}${RUN_ID} case_normalize=${True}
Should Be Equal ${actual} ${expected}
${temp}= Normalize Path ${TEMPDIR} case_normalize=${True}
Should Start With ${actual} ${temp}${/}
Initialize Or Resume Workflow
Resolve Workflow Paths
Validate Run Identity And Root
${exists}= Run Keyword And Return Status Directory Should Exist ${WORK_ROOT}
IF not $exists
Create Directory ${INBOX_DIR}
Create Directory ${OUTBOX_DIR}
Create Directory ${APPROVAL_DIR}
Create Directory ${PENDING_DIR}
Create Directory ${APPROVED_DIR}
Create Directory ${COMPLETED_DIR}
Create Directory ${LEDGER_DIR}
Create File ${OWNER_FILE} ${RUN_ID}${\n} encoding=UTF-8
Create File ${INBOX_DIR}${/}WI-001.txt id=WI-001${\n}payload=alpha${\n} encoding=UTF-8
Create File ${INBOX_DIR}${/}WI-002.txt id=WI-002${\n}payload=beta${\n} encoding=UTF-8
Create File ${PENDING_DIR}${/}WI-001.state pending${\n} encoding=UTF-8
Create File ${PENDING_DIR}${/}WI-002.state pending${\n} encoding=UTF-8
Create File ${APPROVAL_DIR}${/}WI-001.approved
... decision=APPROVE${\n}operator=training-user${\n}
... encoding=UTF-8
ELSE
Verify Owned Existing Workflow State
END
Verify Owned Existing Workflow State
Resolve Workflow Paths
Validate Run Identity And Root
Directory Should Exist ${WORK_ROOT}
Directory Should Exist ${INBOX_DIR}
Directory Should Exist ${OUTBOX_DIR}
Directory Should Exist ${APPROVAL_DIR}
Directory Should Exist ${PENDING_DIR}
Directory Should Exist ${APPROVED_DIR}
Directory Should Exist ${COMPLETED_DIR}
Directory Should Exist ${LEDGER_DIR}
File Should Exist ${OWNER_FILE}
File Should Exist ${INBOX_DIR}${/}WI-001.txt
File Should Exist ${INBOX_DIR}${/}WI-002.txt
${owner}= Get File ${OWNER_FILE} encoding=UTF-8
${owner}= Strip String ${owner}
Should Be Equal ${owner} ${RUN_ID}
... msg=Run ID does not own this workflow root: ${WORK_ROOT}
Process Batch Idempotently
FOR ${item_id} IN @{WORK_ITEMS}
Process One Work Item ${item_id}
IF $INTERRUPT_AFTER == $item_id
Fatal Error Injected interruption after durable checkpoint for ${item_id}
END
END
Process One Work Item
[Arguments] ${item_id}
${input}= Set Variable ${INBOX_DIR}${/}${item_id}.txt
${approval}= Set Variable ${APPROVAL_DIR}${/}${item_id}.approved
${pending}= Set Variable ${PENDING_DIR}${/}${item_id}.state
${approved}= Set Variable ${APPROVED_DIR}${/}${item_id}.state
${output}= Set Variable ${OUTBOX_DIR}${/}${item_id}.txt
${ledger_entry}= Set Variable ${LEDGER_DIR}${/}${item_id}.entry
${done}= Set Variable ${COMPLETED_DIR}${/}${item_id}.done
${already_done}= Run Keyword And Return Status File Should Exist ${done}
IF $already_done
File Should Exist ${output}
File Should Exist ${ledger_entry}
${pending_exists}= Run Keyword And Return Status File Should Exist ${pending}
IF $pending_exists
Remove File ${pending}
END
${approved_exists}= Run Keyword And Return Status File Should Exist ${approved}
IF $approved_exists
Remove File ${approved}
END
Log ${item_id}: durable completion marker exists; safe no-op on resume.
RETURN
END
File Should Exist ${input}
Wait Until Keyword Succeeds 5 seconds 250 milliseconds
... File Should Exist ${approval}
${decision}= Get File ${approval} encoding=UTF-8
${decision_line}= Get Lines Containing String ${decision} decision=
${decision_line}= Strip String ${decision_line}
Should Be Equal ${decision_line} decision=APPROVE
${pending_exists}= Run Keyword And Return Status File Should Exist ${pending}
IF $pending_exists
Move File ${pending} ${approved}
ELSE
File Should Exist ${approved}
END
${source}= Get File ${input} encoding=UTF-8
${stamp}= Get Current Date time_zone=UTC result_format=%Y-%m-%dT%H:%M:%S.%fZ
${body}= Catenate SEPARATOR=${\n} ${source} status=completed completed_at=${stamp}
Create File ${output} ${body}${\n} encoding=UTF-8
${verified}= Get File ${output} encoding=UTF-8
Should Contain ${verified} id=${item_id}
Should Contain ${verified} status=completed
Create File ${ledger_entry} ${item_id}|completed|${stamp}${\n} encoding=UTF-8
Create File ${done} id=${item_id}${\n}completed_at=${stamp}${\n} encoding=UTF-8
${approved_exists}= Run Keyword And Return Status File Should Exist ${approved}
IF $approved_exists
Remove File ${approved}
END
Verify All Items Completed
FOR ${item_id} IN @{WORK_ITEMS}
File Should Exist ${COMPLETED_DIR}${/}${item_id}.done
File Should Exist ${OUTBOX_DIR}${/}${item_id}.txt
File Should Exist ${LEDGER_DIR}${/}${item_id}.entry
END
${pending}= List Directory ${PENDING_DIR}
${approved}= List Directory ${APPROVED_DIR}
Should Be Empty ${pending}
Should Be Empty ${approved}
Preserve Workflow Evidence
${ledger_1}= Get File ${LEDGER_DIR}${/}WI-001.entry encoding=UTF-8
${ledger_1}= Strip String ${ledger_1}
${ledger_2}= Get File ${LEDGER_DIR}${/}WI-002.entry encoding=UTF-8
${ledger_2}= Strip String ${ledger_2}
${ledger}= Catenate SEPARATOR=${\n} id|state|timestamp ${ledger_1} ${ledger_2}
Create File ${OUTPUT DIR}${/}workflow-ledger.txt ${ledger}${\n} encoding=UTF-8
${inbox}= List Directory ${INBOX_DIR}
${outbox}= List Directory ${OUTBOX_DIR}
${approvals}= List Directory ${APPROVAL_DIR}
${pending}= List Directory ${PENDING_DIR}
${approved}= List Directory ${APPROVED_DIR}
${completed}= List Directory ${COMPLETED_DIR}
${tree_text}= Catenate SEPARATOR=${\n}
... inbox=${inbox}
... outbox=${outbox}
... approvals=${approvals}
... pending=${pending}
... approved=${approved}
... completed=${completed}
Create File ${OUTPUT DIR}${/}workflow-tree.txt ${tree_text}${\n} encoding=UTF-8
Guarded Cleanup Workflow Root
Verify Owned Existing Workflow State
Verify All Items Completed
${actual}= Normalize Path ${WORK_ROOT} case_normalize=${True}
${expected}= Normalize Path ${TEMPDIR}${/}${ROOT_PREFIX}${RUN_ID} case_normalize=${True}
Should Be Equal ${actual} ${expected}
${temp}= Normalize Path ${TEMPDIR} case_normalize=${True}
Should Start With ${actual} ${temp}${/}
Preserve Workflow Evidence
Remove Directory ${WORK_ROOT} recursive=${True}
Directory Should Not Exist ${WORK_ROOT}
Read the resource as a state machine.
Resolve Workflow Paths calculates paths only.
Initialize Or Resume Workflow creates synthetic state
exactly once or validates the existing owner marker on a restart.
Process One Work Item checks the completion marker
before mutation, waits only a bounded five seconds for approval,
creates a deterministic outbox path, verifies the output, then
writes the durable completion marker and ledger entry.
The interruption hook runs after the checkpoint. That placement is deliberate: the restart exercise proves the second process can observe the marker and skip the already-completed item.
4. Task suite and explicit cleanup task
*** Settings ***
Resource ../resources/workflow.resource
Suite Setup Initialize Or Resume Workflow
Task Timeout 30 seconds
*** Tasks ***
Process Synthetic Work Items
Process Batch Idempotently
*** Settings ***
Resource ../resources/workflow.resource
*** Tasks ***
Cleanup Completed Workflow
Verify Owned Existing Workflow State
Guarded Cleanup Workflow Root
The normal workflow has no teardown that recursively deletes its durable state. That is not an omission: deleting durable state during failure would destroy the information required to resume. Cleanup is a separate operator action that first verifies ownership and completion of every item.
5. First run: process WI-001, checkpoint it, then inject interruption
# Expected result: non-zero because Fatal Error is injected after WI-001 checkpoint.
python -m robot --rpa --variable RUN_ID:demo-001 --variable INTERRUPT_AFTER:WI-001 --outputdir evidence/first tasks/workflow.robot
Expected durable state after the failed run:
outbox/WI-001.txt exists;
state/completed/WI-001.done exists; the durable ledger
directory contains exactly one WI-001 entry file; WI-002 remains
pending; no WI-002 output exists. Preserve
evidence/first/output.xml, log.html, and
report.html. The first failure is evidence, not trash.
6. Human approval simulation: create WI-002 decision outside Robot
# Cross-platform Python command executed manually by the operator.
python -c "from pathlib import Path; import tempfile; p=Path(tempfile.gettempdir())/'rf19-rpa-demo-001'/'approvals'/'WI-002.approved'; p.write_text('decision=APPROVE\noperator=training-user\n', encoding='utf-8'); print(p)"
This manual command represents the approval boundary. Robot does not manufacture the decision. The decision record has a work-item identity and synthetic operator identity, and it persists independently of Robot memory.
Optional interactive demonstration: on a local
desktop only, Dialogs.Execute Manual Step or
Get Selection From User can pause a task. Do not
place that path in unattended CI; use a durable approval
service/file/queue for operational automation.
7. Resume: completed work is a no-op, pending work advances
python -m robot --rpa --variable RUN_ID:demo-001 --variable INTERRUPT_AFTER:NONE --outputdir evidence/resume tasks/workflow.robot
The log should show WI-001 taking the completion-marker branch and
returning without rewriting its checkpoint or ledger row. WI-002
observes its approval, transitions pending → approved → completed,
writes outbox/WI-002.txt, then records its checkpoint.
The resumed task passes.
8. Third run: prove stable no-op behavior
python -m robot --rpa --variable RUN_ID:demo-001 --outputdir evidence/idempotent tasks/workflow.robot
Both items should take the completion-marker branch. The durable ledger directory should still have exactly one deterministic entry file per item. This is a stronger check than “the command passed twice”: it proves repeated invocation does not create additional synthetic business effects.
9. Before/after evidence matrix
| Point | WI-001 | WI-002 | Robot evidence |
|---|---|---|---|
| Before first run | pending + preapproved | pending, no approval | none |
| After injected interruption | outbox + completed marker | still pending | first run FAIL retained |
| Before resume | already complete | approval file added manually | first failure still preserved |
| After resume | safe no-op | outbox + completed marker | resume PASS |
| After third run | safe no-op | safe no-op | idempotency PASS |
10. Cleanup is an explicit verified operator action
python -m robot --rpa --variable RUN_ID:demo-001 --outputdir evidence/cleanup tasks/cleanup.robot
The cleanup task refuses deletion if a completion marker is missing,
if pending/approved markers remain, if the owner file does not match
RUN_ID, or if the normalized path does not sit below
${TEMPDIR}. Before deletion it copies the ledger and
tree into the Robot output directory.
11. Challenge: choose the correct layer
Add WI-003, but make its approval explicitly
REJECT. Decide where rejection should be represented
and what final task status should be. A strong design keeps the
human decision in durable external state, does not create an
outbox/completion marker for a rejected item, and records a
diagnosable Robot result rather than silently treating rejection as
completion.
Knowledge check
Why does the workflow not delete its temporary root in Suite Teardown?
That root is durable workflow state required for resume and forensic evidence. Automatic deletion on failure could make recovery impossible and erase unprocessed inputs.
Why is WI-001 not duplicated on the second run?
The stable completion marker is checked before mutation, and the deterministic outbox path represents the same logical effect. The resume path returns before appending another ledger row.
What does the approval file own that a Boolean suite variable does not?
A durable, independently readable decision record tied to a work-item ID and operator identity; it survives the Robot process.
Why is Fatal Error injected after the completion marker?
The checkpoint exercise is specifically proving restart after a known durable commit point. If interruption occurred before the marker, the design would need to reason about a possibly-partial effect.
What proves cleanup cannot erase an unrelated temporary directory?
RUN_ID must match a safe character pattern, the owner file must match RUN_ID, the normalized path must equal the exact TEMPDIR-derived expected root, and every work item must already be complete before recursive deletion.
12. Summary and next lesson
You now have a local RPA workflow that survives a failed Robot process and resumes without duplicate effects. Lesson 3 turns those concrete mechanics into design choices: when to use tasks, when to split work, how to choose durable state, and where human review belongs.
References and version anchors
- Robot Framework 7.4.2 — Creating tasks — task syntax/settings
- Robot Framework 7.4.2 — Task execution — --rpa and task result terminology
- Robot Framework 7.4.2 BuiltIn — bounded retry and Fatal Error
- Robot Framework 7.4.2 OperatingSystem — local file/directory state
- Robot Framework 7.4.2 Dialogs — optional interactive user input
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.