Hub Data Dictionary
What the Hub stores about runs, and how to query it. Written for people and AI assistants that answer questions such as "what failed last night, and where?". Start with the views; the tables behind them are listed after.
Query them with kinenix hub query (hub.md, Querying Runs), for example kinenix hub query v_failures --since yesterday, or with any SQLite tool on ~/.kinenix/hub.db. User views (custom_*) are added from ~/.kinenix/hub_views/.
A test (kinenix-hub/tests/test_data_dictionary.py) fails when a table, view, column, or details key exists without a row here, or the other way round. kinenix hub query --describe shows this document's meanings, from a copy in the package (kinenix-hub/kinenix_hub/hub_data.md); copy this file there after editing it, a test checks that they are the same.
How the data gets here
A worker sends one execution log per run when the flow ends (logging.md). The Hub keeps the log whole in executions.raw_log and stores its facts as rows: one in executions, one in step_results per executed step (also every loop iteration, subflow steps, and On Error and Finally steps), and one in failures per failure (also failures the flow continued after).
- The worker removes secrets and personal data before sending (logging.md, Redaction).
- Step outputs and step errors are stored at most 500 characters long, ending with
...when cut; the full log stays inexecutions.raw_log. - Runs from workers older than 0.2.0b4 have fewer facts: no run id, Kinenix version, work items, line, path, subflow, loop item, or retries. Their step outputs are not stored, because those workers did not redact them. Only the failure that stopped such a run has
handled = stopped; other failed steps have an emptyhandled. - When the Hub is upgraded, runs it already stored get their rows from
raw_logonce, at the first start.
Conventions
- Times are UTC, stored as text
YYYY-MM-DD HH:MM:SS.ffffff, so they sort and compare as text:failed_at >= datetime('now', '-1 day'). sectionis where a step is inflow.md:steps,on_error(## On Error), orfinally(## Finally).handledis what happened after a step failed:stopped(the run stopped; On Error ran),continued(on_error: continue, or a step in On Error or Finally),fallback(fallback_step_idran).detailscolumns are JSON with a version keyv; the views expose their keys as columns. The views exist on SQLite only.
Views
v_runs
One row per run. Answers: how many runs, which failed, success rate and duration per flow and worker, how many work items were done.
| Column | Meaning | Example |
|---|---|---|
execution_id | Id of the run on the Hub | 53a58daa-109f-44fa-8d07-b9c0270dc9be |
run_id | Id of the run on the worker (${run.id}, also in log file names) | 20261012T020000-abcd |
flow_name | Flow | TIPH Quotes |
worker_id | Worker that ran it | rpi4 |
status | success or failed | failed |
started_at | Start, UTC | 2026-10-11 19:00:00.000000 |
ended_at | End, UTC | 2026-10-11 19:14:00.000000 |
duration_seconds | Duration | 840.0 |
kinenix_version | Kinenix version on the worker | 0.2.0b4 |
items_total | Work items (iterations of loops with item_label) | 12 |
items_done | Work items done | 6 |
items_failed | Work items that failed; the rest were not reached | 1 |
step_count | Steps executed | 48 |
failure_count | Failures, also continued ones | 3 |
failed_step_name | Step that failed the run | Save Quotes |
error_type | Technical or Business, for the failure that failed the run | Technical |
error_message | Message of the failure that failed the run | File is open in Excel |
report_md | Short Markdown report of the run (see Run report) | # TIPH Quotes, ... |
v_failures
One row per failure. Answers: what failed, where, on which item, how it was handled, and what to do about it.
| Column | Meaning | Example |
|---|---|---|
failure_id | Id of the failure | 17 |
execution_id | Run on the Hub | 53a58daa-... |
run_id | Run id on the worker | 20261012T020000-abcd |
failed_at | Time of the failure, UTC | 2026-10-11 19:07:42.000000 |
flow_name | Flow | TIPH Quotes |
worker_id | Worker | rpi4 |
section | steps, on_error, or finally | steps |
step_name | Step that failed | Choose Brand |
action | Its action | web.click |
step_path | Flow, loops, conditions, and subflows down to the step | TIPH Quotes > Each Request > process_one > Choose Brand |
subflow | Subflow the step is in, or empty | process_one |
line | Line of the step heading in its flow.md | 42 |
loop_item | Item of the innermost loop (from item_label) | R007 HONDA HR-V |
loop_index | Its position, from 1 | 7 |
loop_of | Number of items in that loop | 12 |
retries | Retries made before the step gave up | 2 |
handled | stopped, continued, or fallback | continued |
error_type | Technical or Business | Technical |
exception | Exception class | TimeoutError |
message | Error message | Timeout 30000ms exceeded waiting for option HONDA |
root_cause | Rule-based diagnosis on the worker | Element did not appear in time |
suggested_fix | What to try | Check the selector or raise the timeout |
screenshot | Screenshot path on the worker | ./errors/20261012T020000-abcd_step_3.png |
run_status | Status of the whole run | failed |
v_steps
One row per executed step. Answers: slowest steps, steps that fail most, what a run did in order, which items were skipped and why.
| Column | Meaning | Example |
|---|---|---|
step_result_id | Id of the step row | 412 |
execution_id | Run on the Hub | 53a58daa-... |
run_id | Run id on the worker | 20261012T020000-abcd |
flow_name | Flow | TIPH Quotes |
worker_id | Worker | rpi4 |
seq | Order within the run, from 1, by the time the step ended (a subflow's steps come before its flow.call step) | 5 |
section | steps, on_error, or finally | steps |
step_id | Step id in the compiled flow | sub_step_2_1 |
step_name | Step | Choose Brand |
action | Action | web.click |
status | success, failed, or skipped | success |
started_at | Start, UTC | 2026-10-11 19:07:12.000000 |
duration_seconds | Duration | 1.42 |
step_path | Flow, loops, conditions, and subflows down to the step | TIPH Quotes > Each Request > Choose Brand |
subflow | Subflow the step is in, or empty | process_one |
line | Line of the step heading in its flow.md | 42 |
loop_item | Item of the innermost loop | R007 HONDA HR-V |
loop_index | Its position, from 1 | 7 |
loop_of | Number of items in that loop | 12 |
retries | Retries made | 0 |
skipped_reason | Why a skipped step did not run | Condition '${req.brand} is empty' not met |
error | Error of a failed step, at most 500 characters | Timeout 30000ms exceeded |
output | Output of the step as text, at most 500 characters | {"rows": 12} |
Tables
executions
One row per run, written when the worker sends the run.
| Column | Meaning | Example |
|---|---|---|
id | Id of the run on the Hub | 53a58daa-... |
worker_id | Worker | rpi4 |
flow_name | Flow | TIPH Quotes |
status | success or failed | failed |
start_time | Start, UTC | 2026-10-11 19:00:00.000000 |
end_time | End, UTC | 2026-10-11 19:14:00.000000 |
duration_seconds | Duration | 840.0 |
has_error | 1 when the run failed | 1 |
failed_step_id | Step that failed the run | step_3 |
failed_step_name | Its name | Save Quotes |
error_type | Technical or Business | Technical |
exception_class | Exception class | PermissionError |
error_message | Error message | File is open in Excel |
screenshot_path | Screenshot path on the worker | ./errors/step_3.png |
raw_log | The whole execution log as sent (logging.md) | {"flow_name": ...} |
ai_category | Failure category from the Hub's failure analysis, a Thai label | หาปุ่มหรือ Element ไม่พบบนหน้าเว็บ (โครงสร้างเปลี่ยน) |
ai_confidence | Its confidence, 0 to 100 | 85.0 |
ai_summary | Short explanation, in Thai | |
ai_root_cause | Likely cause, in Thai | |
ai_suggestion | Suggested fix, in Thai | |
created_at | When the Hub received the run, UTC | 2026-10-11 19:14:01.000000 |
details | JSON, keys below | {"v": 1, "run_id": ...} |
executions.details
| Key | Meaning | Example |
|---|---|---|
v | Version of this JSON | 1 |
run_id | Run id on the worker | 20261012T020000-abcd |
kinenix_version | Kinenix version on the worker | 0.2.0b4 |
items_total | Work items | 12 |
items_done | Work items done | 6 |
items_failed | Work items failed | 1 |
report_md | Run report | # TIPH Quotes, ... |
step_results
One row per executed step.
| Column | Meaning | Example |
|---|---|---|
id | Row id | 412 |
execution_id | Run (executions.id) | 53a58daa-... |
seq | Order within the run, from 1 | 5 |
section | steps, on_error, or finally | steps |
step_id | Step id | sub_step_2_1 |
step_name | Step | Choose Brand |
action | Action | web.click |
status | success, failed, or skipped | failed |
started_at | Start, UTC | 2026-10-11 19:07:12.000000 |
duration_seconds | Duration | 31.2 |
details | JSON, keys below | {"v": 1, "line": 42, ...} |
step_results.details
| Key | Meaning | Example |
|---|---|---|
v | Version of this JSON | 1 |
line | Line of the step heading in its flow.md | 42 |
path | Flow, loops, conditions, and subflows down to the step | TIPH Quotes > Each Request > Choose Brand |
subflow | Subflow the step is in | process_one |
loop | Innermost loop item: index, of, item | {"index": 7, "of": 12, "item": "R007 HONDA HR-V"} |
retries | Retries made | 2 |
skipped_reason | Why a skipped step did not run | Condition '...' not met |
error | Error of a failed step, at most 500 characters | Timeout 30000ms exceeded |
output | Output as text, at most 500 characters (not for runs from workers older than 0.2.0b4) | {"rows": 12} |
failures
One row per failure, also failures the flow continued after, so a run that succeeded can still report failed items. A flow.call step whose subflow failed has no row of its own: the failure is the subflow step's.
| Column | Meaning | Example |
|---|---|---|
id | Row id | 17 |
execution_id | Run (executions.id) | 53a58daa-... |
step_result_id | The failed step (step_results.id); empty when the run failed before any step, for example a flow that needs a newer Kinenix | 415 |
failed_at | Time of the failure, UTC | 2026-10-11 19:07:42.000000 |
flow_name | Flow, copied from the run | TIPH Quotes |
worker_id | Worker, copied from the run | rpi4 |
step_name | Step | Choose Brand |
action | Action | web.click |
error_type | Technical or Business | Technical |
handled | stopped, continued, or fallback | continued |
message | Error message | Timeout 30000ms exceeded |
details | JSON, keys below | {"v": 1, "exception": ...} |
failures.details
| Key | Meaning | Example |
|---|---|---|
v | Version of this JSON | 1 |
step_id | Step id | step_1 |
section | steps, on_error, or finally | steps |
exception | Exception class | TimeoutError |
root_cause | Rule-based diagnosis on the worker | Element did not appear in time |
suggested_fix | What to try | Check the selector |
path | Flow, loops, conditions, and subflows down to the step | TIPH Quotes > Each Request > process_one > Choose Brand |
subflow | Subflow the step is in | process_one |
line | Line of the step heading | 42 |
loop | Innermost loop item: index, of, item | {"index": 7, "of": 12, "item": "R007 HONDA HR-V"} |
retries | Retries made | 2 |
screenshot | Screenshot path on the worker | ./errors/step_1.png |
on_error | For the failure that stopped the run: each On Error step with step_name, action, status | [{"step_name": "Email Error", "action": "email.send", "status": "success"}] |
workers
One row per worker, updated by its heartbeats.
| Column | Meaning | Example |
|---|---|---|
id | Worker id (KINENIX_WORKER_ID, default the host name) | rpi4 |
name | Display name | Raspberry Pi 4 |
ip_address | Address it reported | 192.168.1.50 |
os_info | Operating system | Linux 6.1 |
status | online or busy as last reported; shown as offline when heartbeats stop | online |
current_task | Flow it is running, or empty | TIPH Quotes |
cpu_percent | CPU use | 12.5 |
ram_usage | Memory use | 1.2 / 8 GB |
last_heartbeat | Last heartbeat, UTC | 2026-10-12 01:59:30.000000 |
schema_version
Schema changes applied to this database (hub.md, Upgrades).
| Column | Meaning | Example |
|---|---|---|
version | Migration number | 2 |
name | What it changed | run records: executions.details, step_results, failures |
applied_at | When, UTC, ISO 8601 | 2026-10-14T06:50:28+00:00 |
Run report
Each run gets a short Markdown report in executions.details.report_md (v_runs.report_md), for people, e-mail, and AI. Times are the worker's local time. kinenix hub report --flow NAME prints the report of a flow's latest run.
# TIPH Quotes, 2026-10-12 02:00 +07:00 (failed)
- Worker: rpi4, Kinenix 0.2.0b4, duration 14 min
- Items: 6 of 12 done, 1 failed, 5 not reached
- Failed: R007 HONDA HR-V at "Choose Brand" (subflow process_one, line 42, after 2 retries), continued: Timeout 30000ms exceeded
- Failed: "Save Quotes" (line 60), run stopped: File is open in Excel
- On Error: Capture Screen succeeded, Email Error succeeded
- Finally: Close Browser succeeded
At most 10 failures are listed; the rest are counted. A run without failures says - No failures.
Example questions
-- What failed since yesterday, and where?
SELECT failed_at, flow_name, worker_id, step_path, loop_item, handled, message
FROM v_failures WHERE failed_at >= datetime('now', '-1 day') ORDER BY failed_at;
-- Success rate and average duration per flow over the last 7 days
SELECT flow_name, COUNT(*) AS runs, AVG(status = 'success') AS success_rate, AVG(duration_seconds) AS avg_seconds
FROM v_runs WHERE started_at >= datetime('now', '-7 days') GROUP BY flow_name;
-- Steps that fail most
SELECT flow_name, step_name, action, COUNT(*) AS failures
FROM v_failures GROUP BY flow_name, step_name, action ORDER BY failures DESC LIMIT 10;
-- Slowest steps on average
SELECT flow_name, step_name, AVG(duration_seconds) AS avg_seconds, COUNT(*) AS runs
FROM v_steps WHERE status = 'success' GROUP BY flow_name, step_name ORDER BY avg_seconds DESC LIMIT 10;