Continuous Task Solving (Living Tasks)
A normal task (Task Solving Guide) ends with a report. A living task keeps working after that report: every day, or whenever new data arrives, it pulls fresh evidence, recomputes its KPIs, checks for drift and asks an agent to look only when something changed. You review what it found and decide what becomes the new baseline.
Read the Task Solving Guide first. This guide assumes you already have a task
folder with a results.json.
Contents
- How it works
- Before you start
- Try it in five minutes
- Make your own task living
- Set the goal
- Write the cycle plan
- Write a stage script
- Backtest before you trust it
- Run cycles by hand
- Make it run by itself
- Standard-first gate for scheduled tasks
- Let an agent handle the triggers
- Day-to-day work with a living task
- Company data sources
- Troubleshooting
- Command reference
1. How it works
The principle is program the loop, prompt the exceptions. The runner is plain Python and makes no LLM calls. An agent runs only when a trigger fires. People make every decision that changes the baseline.
one cycle (daily, on new data, or on demand)
sense -> evidence -> refresh -> script:<model> -> kpis -> drift -> goal -> diff -> ledger
-> digest -> notify -> agent (only when a trigger fired)
writes: cycles/<id>/digest.md and LIVING_REPORT.md (always current)
you: read the report -> decide ledger items -> promote a cycle to the baseline
Two ways to run cycles:
| Mode | Command | Use it to |
|---|---|---|
| Monitor | neqsim task-cycle |
Keep a finished study current: new data, new KPIs, drift alarms |
| Solve | neqsim task-solve |
Iterate until the goal is met, or until further rounds gain too little |
After a solve loop stops, the task goes back to monitoring. It reopens by itself when the objective regresses, new evidence arrives, the brief changes, NeqSim changes, or a reviewer asks.
Everything lives in one continuous/ folder beside the normal three steps.
Nothing in step1_*, step2_* or step3_* is changed by a cycle.
continuous/
cycle_plan.yaml what each cycle does (you edit)
goal.yaml what "solved" means (you confirm)
stages/*.py task-local stage scripts (you write)
LIVING_REPORT.md always-current summary (generated, never edit)
report/ KPI trend figure (generated)
baseline/ promoted baseline + history
evidence/ accepted file hashes and provenance baseline
ledger/events.jsonl improvement ledger (append-only)
cycles/<id>/ one folder per cycle: cycle.json, kpis.json, triggers.json, digest.md
backtests/<run>/ isolated replay runs
data/<source>/ pulled evidence, partitioned by year/month
state.json watermarks.json drift_state.json kpi_history.csv
2. Before you start
| You need | Why | Check |
|---|---|---|
The shared Python environment (with pyyaml) |
Runs the runner and stage scripts | C:\appl\neqsim-venv\Scripts\python.exe -c "import yaml" |
The neqsim CLI |
All task-* commands |
neqsim task-status --help |
| A NeqSim source checkout | Runner code lives in devtools/neqsim_continuous/ |
neqsim --show-task-root |
| Java (only for stages that call NeqSim) | NeqSim thermodynamics | python devtools/java_locator.py |
| GitHub Copilot CLI, signed in (only for automatic agent triage) | Headless agent sessions | copilot --version |
| A community or enterprise adapter (only for live historian data) | Pulls data from PI/IP.21, OTS, PDM | see section 14 |
If neqsim is not on PATH in your shell, use the identical command through
the interpreter:
C:\appl\neqsim-venv\Scripts\python.exe devtools\neqsim_cli.py task-status <task>
Nothing else is needed for the reference case: it uses a CSV file drop and the Python standard library only.
Where living tasks are created and found
Living tasks use the same task root as every other task — the folder shown by
neqsim --show-task-root (set it once with neqsim --set-task-root "PATH", or
for one shell with NEQSIM_TASK_ROOT). So you never have to type full paths:
| You type | The runner uses |
|---|---|
neqsim task-cycle C:\work\my_task |
That folder (an existing path always wins) |
neqsim task-cycle 2026-09-24_my_task |
<task root>\2026-09-24_my_task |
neqsim task-status |
Every living task in the task root |
neqsim task-reference-case |
Creates <task root>\reference_compressor_station |
This works from any folder. If the name is found in neither place, the error names both locations it looked in.
3. Try it in five minutes
The public reference case is a synthetic compressor station with three faults injected at known dates (efficiency decline, flow-meter bias, suction-pressure step). Use it to learn the loop before you touch a real task.
neqsim task-reference-case # created in your task root
$task = "reference_compressor_station" # a name inside the task root is enough
# 1. Replay a year of data and check the monitor finds the three faults
neqsim task-backtest $task --start 2025-10-02 --end 2026-09-30
# 2. Run one monitor cycle as if it were the last day of the data
neqsim task-cycle $task --now 2026-09-30T05:00:00Z
# 3. Solve until improvement is marginal
neqsim task-solve $task --until converged
# 4. Look at what it found
neqsim task-status $task
neqsim task-ledger $task list
Expected results:
- Backtest: 3 of 3 faults detected (delays 13, 2 and 2 days), 0 missed, 0 false alarms per month.
- Cycle: triggers
kpi_step:suction_pressure_baraandcriterion:polytropic_efficiency, a new ledger item “Wash or inspect the compressor”, andNeeds a decision: yes. - Solve: stops in state
convergedafter 8 rounds, because the last gains and the best expected gain are below the tolerance.
Open continuous/LIVING_REPORT.md to see all of this in one page.
To keep the experiment out of your task root, give a folder:
neqsim task-reference-case C:\tmp\living, then use the full path
C:\tmp\living\reference_compressor_station in the commands above.
4. Make your own task living
Start from a finished (or nearly finished) task folder:
neqsim task-living <task> --brief path\to\brief.docx
This never overwrites an existing file. It creates:
| File | Content |
|---|---|
continuous/cycle_plan.yaml |
A commented template of every section |
continuous/goal.yaml |
A draft goal compiled from the brief headings (unconfirmed) |
continuous/baseline/ |
The first baseline, taken from the numeric key_results in results.json |
continuous/ledger/events.jsonl |
One proposed item per results.json recommendations entry |
study_config.yaml |
A continuous: block that marks the task as living |
--brief is optional. Without it, the brief recorded in study_config.yaml
(inputs.prompt_file, set by neqsim new-task --prompt-file) is used. The brief
is copied into step1_scope_and_research/references/manual/ and its hash is
stored, so an edited brief later raises a brief_changed trigger.
Then do sections 5 to 8, in that order.
5. Set the goal
goal.yaml says what “solved” means. A solve loop refuses to run until
somebody confirms it.
confirmed_by: "A. Engineer" # required before task-solve
objective:
metric: power_reduction_pct # a KPI produced by a stage
direction: maximize # or minimize
target: 2.0 # goal_met when reached (validated)
confidence_required: medium
constraints: ["suction temperature >= 22 C"]
stop:
converged: {window: 3, relative: 0.02, absolute: 0.01}
budget: {iterations: 12, agent_sessions: 6}
expected_improvement_cost: 0.01 # a candidate must be worth at least this
reopen:
regress_margin: 0.5 # reopen if the metric falls this far below the baseline
on: [new_evidence, brief_changed, neqsim_capability, reviewer_request]
The solve loop stops in one of five states:
| State | Meaning |
|---|---|
goal_met |
The target was reached with a validated result at the required confidence |
converged |
Recent gains are below max(absolute, relative * abs(J)) and no candidate is worth its cost |
infeasible |
The reachable upper bound reported by the solver is below the target |
blocked |
Only actions that need a person are left |
budget_exhausted |
The round or agent-session budget ran out |
Use neqsim task-solve <task> --allow-unconfirmed only for experiments.
6. Write the cycle plan
cycle_plan.yaml is the only file the runner reads to know what to do. Every
section is optional; start small and add sections as you need them.
6.1 Sources — where new data comes from
sources:
station:
adapter: file # built in: CSV files dropped in a folder
options: {folder: data_drop, time_column: timestamp, pattern: "*.csv"}
initial_lookback_days: 1 # first pull goes this far back
stale_after_hours: 48 # no rows for this long -> source "stale"
Each source keeps a watermark. A pull only moves the watermark forward when
it succeeds, so a failed pull is retried with the same window next cycle.
Relative folders are resolved against the task folder. Put large data outside
synced folders with data_root: "D:/neqsim-data/<task>".
6.2 KPIs — what each cycle measures
kpis:
suction_pressure_bara: {source: station, column: suction_pressure_bara, agg: mean}
headroom_MSm3d: {from: results, key: headroom_to_98pct_power_MSm3d}
agg is mean, min, max, last, sum or count. KPIs computed by a stage
script (section 7) need no entry here.
6.3 Drift — when a KPI has really moved
drift:
signals: [polytropic_efficiency, suction_pressure_bara]
settings:
lambda: 0.2
L: 3.5
k: 0.5
h: 6.0
warmup: 30 # cycles used to freeze the baseline mean and sigma
confirm: 2 # consecutive out-of-limit cycles before an alarm
signals:
polytropic_efficiency: {min_sigma: 0.002} # engineering floor
suction_pressure_bara: {min_sigma: 0.2}
Drift uses EWMA plus a two-sided CUSUM. Always set min_sigma: a 30-cycle
warm-up underestimates the noise, and without a floor white noise alone raises
false alarms within months. Choose the smallest shift that is worth a review.
6.4 Triggers — simple rules
triggers:
kpi_step: {suction_pressure_bara: 2.0} # |value - baseline| > 2.0
criteria: {polytropic_efficiency: "< 0.785"} # >, <, >=, <=
Step and criterion triggers fire when the rule is crossed, so a persistent state raises one trigger, not one per day.
6.5 Stages — the order of work
stages: [sense, evidence, refresh, "script:station_model", kpis, drift, goal, diff, ledger, digest, notify, agent]
| Stage | Does |
|---|---|
sense |
Detects a changed brief, new reference documents, a new NeqSim commit |
refresh |
Pulls new rows from every source |
script:<name> |
Runs one of your stage scripts |
kpis |
Computes the plan KPIs |
drift |
Updates the drift monitor and raises drift:<signal> alarms |
goal |
Checks whether the task must reopen |
diff |
Compares KPIs with the baseline and the previous cycle; step/criterion triggers |
ledger |
Adds new proposals and tracks KPI values of ledger items |
digest |
Writes digest.md for the cycle |
notify |
Sends the digest to the configured channels |
agent |
Launches a headless agent when a trigger fired (section 11) |
inputs |
Reads the engineers’ comments and restrictions (user_input.yaml, section 17) |
gates, constraints, guard, outcome |
Production loop: model-validity gates, constraint slack, proposal guard, realised-vs-predicted gain (section 17) |
6.6 Notifications
notify:
channels:
- file # always: digest.md
- {type: email, host: smtp.example.com, to: [you@example.com], user: you,
password_env: NEQSIM_SMTP_PASSWORD, when: [needs_decision]}
- {type: teams, url_env: NEQSIM_TEAMS_WEBHOOK, when: [needs_decision, stop_state]}
when is any of every, needs_decision, trigger, stop_state. Secrets are
never written in the plan: give the name of an environment variable. Email
and webhooks are suppressed in dry runs and backtests.
6.7 Solve, backtest and report settings
solve:
stages: [sense, evidence, "script:solver", goal, diff, ledger, digest]
max_rounds: 12
max_branches: 3 # keep at most 3 candidate branches open
critic: {enabled: false} # independent agent review before goal_met/converged
backtest:
expected:
- {trigger: "drift:polytropic_efficiency", onset: "2026-01-29", max_delay_days: 45}
report: {formal: on_promote} # never | on_promote | every_cycle
report.formal controls the Word/HTML report. on_promote is recommended: the
formal report then always matches the promoted baseline. LIVING_REPORT.md is
rewritten after every cycle regardless.
7. Write a stage script
Put task-specific work — a NeqSim model, a calibration, an optimiser — in a
small Python file under continuous/stages/. A stage is one function that takes
the cycle context and returns a dictionary.
"""Stage script: suction gas density from the rows pulled this cycle, computed with NeqSim."""
from neqsim_dev_setup import neqsim_init
def run(ctx):
rows = ctx.new_rows.get("station", [])
if not rows:
return {"status": "warn", "message": "no new rows"}
pressure = sum(r["suction_pressure_bara"] for r in rows) / len(rows)
temperature = sum(r["suction_temperature_C"] for r in rows) / len(rows)
ns = neqsim_init(recompile=False, verbose=False) # reuses the JVM on later cycles
fluid = ns.JClass("neqsim.thermo.system.SystemSrkEos")(273.15 + temperature, pressure)
fluid.addComponent("methane", 0.90)
fluid.addComponent("ethane", 0.07)
fluid.addComponent("propane", 0.03)
fluid.setMixingRule("classic")
ns.JClass("neqsim.thermodynamicoperations.ThermodynamicOperations")(fluid).TPflash()
fluid.initProperties()
return {"kpis": {"suction_density_kg_m3": float(fluid.getDensity("kg/m3"))}}
Register it and add it to the stages:
scripts:
gas_density: {file: continuous/stages/gas_density.py, function: run}
stages: [sense, evidence, refresh, "script:gas_density", kpis, drift, diff, ledger, digest, notify]
What the context gives you:
| Attribute | Content |
|---|---|
ctx.new_rows[source] |
Rows pulled this cycle, numbers already converted |
ctx.kpis |
KPIs computed so far this cycle |
ctx.baseline, ctx.goal, ctx.plan |
The promoted baseline, the goal and the plan |
ctx.task_dir, ctx.cycle_dir, ctx.state_dir |
Task, cycle and continuous/ folders |
ctx.now, ctx.mode, ctx.dry_run |
Cycle clock, monitor/solve/backtest, dry-run flag |
What you may return (all keys optional):
| Key | Effect |
|---|---|
kpis |
KPI values added to this cycle |
proposals |
New ledger items: {"title", "category", "kpi", ...} |
triggers |
Extra trigger names |
status, message |
ok, warn or fail, with a reason |
solve |
Solve-loop result (below) |
A solver stage for task-solve also returns the validated objective, the
reachable bound and the candidate next actions:
return {"kpis": {"power_reduction_pct": value},
"solve": {"value": value, "validated": True, "confidence": "high",
"action": "lower suction temperature to 297 K",
"upper_bound": bound,
"candidates": [{"action": "lower suction temperature further",
"branch": "cooling", "p_success": 0.9,
"predicted_delta": 0.3}]}}
For a full flowsheet, load the model once in the script and use
ProcessAutomation.evaluate() for each trial and getUtilizationSnapshot() for
capacity — both return JSON and never throw, so one bad candidate degrades one
round, not the loop. Keep the heavy model in the script; keep the plan declarative.
8. Backtest before you trust it
A backtest replays archived data with a simulated clock and scores the monitor against the events you expected. Live state is never touched.
neqsim task-backtest <task> --start 2025-10-02 --end 2026-09-30 --repeat
| Option | Meaning |
|---|---|
--step-hours 24 |
Simulated time between cycles |
--name backtest |
Run folder name under continuous/backtests/ (use several names to keep several runs) |
--repeat |
Run twice and report whether the result is reproducible |
The report (continuous/backtests/<run>/backtest_report.md) gives detected and
missed events, detection delay, false alarms per month and reproducibility. Tune
min_sigma and confirm until every expected event is found within its
max_delay_days and false alarms are close to zero. Record the result in
results.json validation. Schedule only after a clean backtest.
8.1 Stop today, resume tomorrow
The task folder is the hand-off, not the chat session. Completed cycle stages, the
improvement ledger, baselines, checkpoints and continuous/state.json are persisted as
work proceeds. On another day — or after copying/synchronising the task folder to another
supported machine — start with:
neqsim task-status <task> # five-second view: conclusion, evidence, changes and next action
neqsim task-resume <task> # continue an interrupted solve/cycle, if there is one
task-status reports the goal, current conclusion, baseline/best validated result,
attempts and rejected hypotheses, blockers, validation/evidence state, what changed since
the last run, the recommended next action, and last/next-run information. It is derived
from persisted task evidence and does not require conversation history.
Persisted task state uses an explicit schema. State written by the earlier 1.0 runner is migrated to schema 1.1 without dropping unknown fields. A task written by a newer incompatible schema fails closed with an upgrade message instead of being silently rewritten. The persisted identity is task-relative and records writer provenance rather than checkout-absolute paths, so moving a task between machines does not invalidate it.
If a process stops after a cycle begins, task-resume reuses the same cycle id and skips
already completed calculation stages even when the wall clock or host changes. Production
gates, constraints and proposal validation run again against the restored values before
advice can be published; a saved successful stage status does not restore its filtering.
The input stage reapplies its frozen restrictions without repeating input side effects.
An unavailable input checkpoint blocks advice. Completed digest,
notification and agent-review metadata are restored when such stages are skipped, so a
resumed cycle cannot erase already recorded side effects. If a solve cycle completed but
the process died before state.json was checkpointed, the solve loop adopts that completed
cycle before starting another attempt. An active continuous/LOCK still protects against
two concurrent writers.
The Status view is the five-second operational summary. The Living Report keeps the understandable history and change detail. The ordinary Task Solver report remains the clean current-best engineering report. The Final Report is an explicitly reviewed, immutable delivery revision generated from that same canonical report pipeline; it excludes transient cycle history and points to the Living Report, ledger, promoted-cycle manifest and work record for auditability.
8.2 Update when evidence changes
Use the task folder as the source of truth when a datasheet, drawing, standard, measurement, requirement, assumption, model or calculation input changes:
neqsim task-status <task> # shows pending files, affected stages/conclusions and next action
neqsim task-update <task> # records provenance and reruns the affected calculation stages
task-living creates continuous/evidence/inventory.json, a task-relative SHA-256 inventory.
The default inventory covers the reference collection, SOURCES.md, scope/analysis files,
user_input.md, study_config.yaml, continuous/goal.yaml and continuous/user_input.yaml.
Override evidence.include or
evidence.exclude in cycle_plan.yaml for task-specific technical inputs. Absolute paths are
never persisted, so the inventory remains portable with the task folder.
The inventory has an explicit schema; an inventory written by a newer incompatible runner
fails closed with an upgrade instruction instead of being silently rewritten.
Map each evidence family to the stages and conclusions it can affect:
evidence:
include:
- "step1_scope_and_research/references/**/*"
- "step2_analysis/models/**/*"
rules:
- name: compressor-datasheet
match: "**/compressor-datasheets/**"
stages: ["script:compressor_model"]
kpis: [compressor_power_MW, driver_margin_pct]
conclusions: ["compressor power", "driver margin"]
dependencies:
"script:compressor_model": [refresh]
When every changed path matches a rule, task-update reruns only the affected stages, their
declared dependencies, and the common KPI/goal/diff/ledger/digest pipeline. List recalculated
metrics under kpis; those values are invalidated before the selected stages run. Other KPIs from
the latest completed cycle are carried forward as unaffected results, with the source cycle recorded in
cycle.json. Added, modified and removed files retain before/after hashes in the immutable cycle
folder. If an affected KPI is not recomputed, the cycle is degraded and the evidence baseline does
not advance. If any changed path is unmapped, the runner fails closed to the full planned cycle; it
never silently assumes that a conclusion is unaffected. A degraded update does not advance the
accepted inventory, so the same change remains pending for a safe retry.
The Living Report shows the file changes, provenance, affected conclusions and rerun scope. The
ordinary Task Solver report remains the current-best engineering report and is regenerated only
through the existing canonical report settings (report.formal) or task-report --formal.
9. Run cycles by hand
neqsim task-cycle <task> # one monitor cycle, now
neqsim task-cycle <task> --dry-run # no watermarks, ledger or drift state written
neqsim task-cycle <task> --stages sense,refresh,kpis,digest
neqsim task-cycle <task> --no-agent # never launch an agent this cycle
neqsim task-solve <task> --until goal # stop at goal_met (or another stop state)
neqsim task-solve <task> --until converged # ignore the target, stop when gains are marginal
neqsim task-solve <task> --reset # forget earlier solve rounds
Each cycle prints its digest. The cycle id is the UTC time and host
(2026-09-30T0500Z@myhost); a rerun in the same minute gets -r2. A crashed
cycle is resumed by running it again: completed calculations are reused and production validation is rerun.
10. Make it run by itself
10.1 On a Windows laptop or workstation
neqsim task-schedule <task> --daily 05:00 # preview the command
neqsim task-schedule <task> --daily 05:00 --install # create the scheduled task
neqsim task-schedule <task> --show # check it
neqsim task-schedule <task> --remove # delete it
--install creates a Windows Task Scheduler entry named
NeqSim living task <folder> that runs
python devtools\neqsim_cli.py task-cycle <task> --mode monitor --standard-first
with the shared interpreter. By default it runs only while you are signed in. To
run it while signed out, open Task Scheduler, select the task and choose
Run whether user is logged on or not (Windows asks for your password there;
the runner never sees it).
Scheduled cycles are intended to maintain a solved engineering task, not replace
the first solve. The generated command therefore includes --standard-first:
before every scheduled cycle, NeqSim checks that the initial Standard task basis
exists and tries to create missing formal report/work-record artifacts with the
normal report tools.
10.2 On a Linux server
Use --every-hours 2 instead of --daily for a recurring interval. Supported
intervals are 1, 2, 3, 4, 6, 8, 12 and 24 whole hours, anchored at local midnight
on both Windows and cron. Other intervals are rejected because cron hour steps
reset each day and would not preserve the requested spacing. Use only one timing
option; daily times must be between 00:00 and 23:59. These are local wall-clock
schedules, so daylight-saving transitions may change elapsed spacing.
Windows runs a quoted continuous/run_cycle.cmd wrapper to support paths with
spaces. If the wrapper path itself exceeds the Task Scheduler command limit,
installation reports a failure and asks for a shorter task path.
task-schedule prints the cron line instead of installing it:
neqsim task-schedule /data/tasks/<task> --daily 05:00
crontab -e # paste the "cron" line from the output
Server checklist:
- Clone NeqSim, build once (
./mvnw compile) and setNEQSIM_PROJECT_ROOT. - Install the Python dependencies and any adapter packages (section 14).
- Set
NEQSIM_CONTINUOUS_HOSTso cycle ids show which machine ran them. - Set
data_rootin the plan to local disk if the task folder is on a share. - Put notification secrets in the service environment, not in the plan.
10.3 Running on more than one machine
Only one cycle runs at a time per task: a continuous/LOCK file blocks a second
run and expires after 6 hours if a run crashed. If a laptop and a server keep
separate copies of a task, merge their ledgers:
neqsim task-ledger <task> merge \\server\tasks\<task>\continuous\ledger\events.jsonl
The ledger is append-only, so a merge is the union of events and never loses a decision.
11. Standard-first gate for scheduled tasks
The Standard-first gate is also available for a manual cycle:
neqsim task-cycle <task> --standard-first
Use it before switching an ad-hoc living task to a server or laptop schedule. The gate checks that the task has:
- Step 1 scope/research files, including capability assessment;
- Step 2 runnable model, notebook or script, or an explicit data-gap blocker;
results.jsonwith assumptions/data gaps;- consistency-check status where available;
- first formal Word/HTML report; and
step3_report/WORK_RECORD.md.
When the report or work record is missing, the gate calls the same configured
tools as the normal workflow (neqsim report, neqsim work-record, and the
consistency checker when present). It does not fabricate missing inputs. If
documents, live data, engineering limits or validation evidence are unavailable,
the correct outcome is a first report that states the data gap and explains what
was assumed instead.
Before judging the basis, the gate runs the automatic document retriever
(devtools/doc_retriever.py, also neqsim fetch-docs <task>) when a retrieval
backend is configured. It infers the installation from the task text, pulls the
highest-ranked P&IDs and data sheets into references/stid/, re-indexes
SOURCES.md, and records a document_retrieval action. A successful retrieval
is reused for 24 h; a failed one is retried after 6 h. The controlled_documents
check shows whether any controlled PDFs are present. Its status file,
references/stid/retrieval_status.json, names the concrete blocker
(no_backend, no_installation, no_matches, auth_error), so “no documents
found” is never reported after searching only the standards library.
The audit is written to continuous/standard_first_status.json:
| Field | Meaning |
|---|---|
ready |
True only when the initial Standard task basis is complete |
readiness |
ready, blocked or incomplete |
checks |
Per-artifact pass/fail map |
hard_missing |
Items that still prevent operational scheduled monitoring |
actions |
Document retrieval, report, work-record and consistency commands attempted by the gate |
If the gate cannot finish the Standard basis, the cycle may still run to collect
evidence, but it is marked degraded and raises a standard_first:* trigger.
Treat that trigger as a work item: finish the report, fill the work-record
narrative, or record the blocker/data gap explicitly.
The gate is deliberately about task readiness, not process correctness. A task can pass Standard-first and still block operational recommendations if its model representativeness, tag map, equipment limits or safety basis are insufficient; those are domain-specific KPIs handled by the cycle plan and stage scripts.
12. Let an agent handle the triggers
When a cycle raises a trigger and the plan enables it, the agent stage starts
a bounded, headless GitHub Copilot CLI session with the
continuous-improvement agent and a triage prompt:
agent:
enabled: true
agent: continuous-improvement
executable: copilot
timeout_s: 1800
# deny: ["shell(git push)", "shell(git commit)", "shell(gh pr create)"] # the default
The session may read the task and write only inside it. It writes
cycles/<id>/agent_review.md and may add ledger items with status proposed.
It cannot push, commit, open pull requests, promote a baseline or accept a
ledger item. The exact command is stored in cycle.json so a reviewer can see
what ran. If copilot is not installed the cycle records not_installed and
continues.
You can also work with the agent interactively in VS Code Copilot Chat: pick continuous-improvement in the agent picker and ask, for example:
- “Make
<task>living, compile the goal from its brief and backtest it.” - “Triage the last cycle of
<task>.” - “Solve
<task>until converged and tell me which cycle to promote.” - “Create the reference case in
C:\tmp\living.”
The agent follows the same rules: it prepares, you decide.
13. Day-to-day work with a living task
13.1 Read the living report
continuous/LIVING_REPORT.md is the one page to open. It is rebuilt after every
cycle, solve round, backtest, promotion, reopen and ledger decision, and shows
the state and stop reason, the goal and baseline, the latest KPIs against the
baseline, solve rounds, KPI trends (report/kpi_trends.png), trigger events,
the ledger with pending decisions, the baseline history, backtests and the
next actions. Never edit it; rebuild it with neqsim task-report <task>.
For all living tasks in a folder at once:
neqsim task-status C:\path\to\task-root
13.2 Triage a triggered cycle
Open continuous/cycles/<id>/:
| File | Tells you |
|---|---|
digest.md |
Short summary and whether a decision is needed |
triggers.json |
Which rules fired |
validation.json |
Drift statistics per signal |
diff_vs_baseline.json |
Every KPI against the baseline and the previous cycle |
refresh_report.json |
What each source returned |
agent_review.md |
The agent’s analysis, if it ran |
Classify each trigger: data fault, model mismatch, real plant change, or new opportunity. A data fault usually means fixing the source, not the plant.
13.3 Decide ledger items
neqsim task-ledger <task> list
neqsim task-ledger <task> show OPP-0002
neqsim task-ledger <task> set OPP-0002 accepted --by "A. Engineer" --note "wash planned week 42"
Allowed transitions:
| From | To |
|---|---|
proposed |
under_review, accepted, rejected, superseded |
under_review |
accepted, rejected, superseded |
accepted |
implemented, rejected, superseded |
implemented |
verified, not_verified |
not_verified |
under_review, superseded |
Categories are operational, maintenance, modification, model, data and
tooling. An item that names a kpi gets that KPI’s value recorded every
cycle, so after implemented you can see whether it worked before you set
verified.
13.4 Promote a cycle to the baseline
neqsim task-promote <task> 2026-09-30T0500Z@myhost --reviewer "A. Engineer" --note "after wash"
The previous baseline is archived under baseline/history/. The cycle’s KPIs
are merged over the old baseline, so promoting a solve round (which reports only
the objective) keeps the baseline of the monitored KPIs. With
report.formal: on_promote the Word/HTML report is regenerated; otherwise run
neqsim report <task>.
13.5 Generate the Final Report
After the latest complete, non-degraded cycle has been reviewed and promoted, and no evidence change is pending:
neqsim task-report <task> --final --reviewer "A. Engineer" --note "Issued for delivery"
This invokes the canonical Task Solver Word/HTML generator; it does not fork or replace the
ordinary report architecture. The ordinary current-best outputs stay in step3_report/.
Final delivery revisions are written separately under step3_report/final/FR-001/,
FR-002/, and so on. Each revision includes Word and self-contained HTML plus
FINAL_REPORT_MANIFEST.json with task-relative provenance and SHA-256 hashes. Add --pdf when
the configured host has a supported Word/LibreOffice PDF renderer.
The command fails closed if a cycle is interrupted, the promoted cycle is degraded, evidence is
pending, results.json differs from the promoted snapshot, or the final-report schema is newer
than the installed runner. Repeating the same finalization is idempotent; a later promoted result
creates the next revision and preserves earlier deliveries. task-status and the Living Report
show the current final revision and whether changed report inputs have made it stale.
The generated report contains a short Final Report Record appendix. It identifies the reviewer, promoted baseline and source cycle, and references—but does not copy—the transient history in:
continuous/LIVING_REPORT.md(history and change view);continuous/ledger/events.jsonl(decision trail);continuous/cycles/<id>/cycle.json(promoted-cycle evidence);step3_report/WORK_RECORD.md(method, sources and reproducibility record).
Multi-day acceptance example
- Day 1: run
task-solve; close VS Code while a later cycle is incomplete. - Day 2: copy or reopen the task folder on another supported machine. Run
task-status, thentask-resume. Persisted task state—not chat history—restores the cycle and completed stages. Review and promote the validated result. - Day 3: add a revised public datasheet under the task references.
task-statusidentifies the changed hash and affected conclusions;task-updateperforms the mapped selective rerun and retains unaffected KPIs. Review and promote the updated result. - Run
task-report --final --reviewer NAME. The ordinary current-best report remains in place, the Living Report retains the three-day history, andFR-001is the standalone delivery.
The synthetic compressor reference case and deterministic devtools tests exercise this sequence, including Word/HTML generation, audit references, idempotent re-issue, stale-report detection and fail-closed newer-schema handling.
13.6 When the task reopens
A monitoring task reopens when the objective drops more than regress_margin
below the baseline, or on new_evidence (the references manifest changed),
brief_changed, neqsim_capability (a new NeqSim commit) or a reviewer request.
The living report shows the reason; run neqsim task-solve <task> to continue.
14. Company data sources
The runner is public-first. The only built-in adapter is file, which reads CSV
exports dropped into a folder, so every task works without company access.
Additional adapters install as Python packages and are found automatically:
| Adapter name | Package | Reads |
|---|---|---|
file |
built in | CSV files in a folder |
tagreader |
neqsim-continuous-improvement-toolkit (community skills) |
OSIsoft PI and Aspen IP.21 via tagreader |
ots |
enterprise-continuous-improvement-adapters (enterprise skills) |
Omnia Timeseries hourly aggregates |
pdm |
enterprise-continuous-improvement-adapters (enterprise skills) |
PDM daily allocated production |
Use the name in sources.<name>.adapter. A plugin can also be named by its
import path, package.module:Class, which works even without package metadata.
A missing adapter is not an error: the source is recorded as not_installed,
the cycle is marked degraded, and it continues.
To write your own adapter, implement a class whose constructor takes the
options and whose pull(since, until, out_dir) returns a SourceResult
(see devtools/neqsim_continuous/adapters.py for the file adapter), then
register it under the entry-point group neqsim_continuous.adapters. Stages and
notifiers use the groups neqsim_continuous.stages and
neqsim_continuous.notifiers the same way.
Never commit continuous/data/ or plant data to a public repository.
15. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Task is locked by ... until ... |
Another cycle is running, or one crashed | Wait, or delete continuous/LOCK if no cycle is running |
goal.yaml is not confirmed |
confirmed_by is empty |
Confirm the goal (section 5) |
goal.yaml has no objective.metric |
The goal was not filled in | Set objective.metric to a KPI a stage produces |
Source stale |
No new rows for stale_after_hours |
Check that the export or historian is still delivering |
Source not_installed |
Adapter package missing | Install it into the shared environment (section 14) |
Cycle degraded |
A stage failed or was skipped, or a source was not ok |
Read cycle.json stage messages; rerun the cycle to resume |
| Final report says evidence changed | Files differ from the accepted evidence inventory | Run task-update, validate the selected rerun, then promote it |
| Final report says results differ from baseline | results.json was edited or regenerated after promotion |
Validate the result and promote its complete cycle before finalizing |
kpis stage warn: no data for ... |
The source returned no rows for that column | Check column and source names in the plan |
| Too many drift alarms | Engineering floor missing or too small | Set or raise min_sigma; raise confirm; backtest again |
Agent not_installed |
copilot is not on PATH of the scheduled task |
Install and sign in to GitHub Copilot CLI, or set executable to its full path |
neqsim not recognized |
CLI not on PATH |
Use python devtools\neqsim_cli.py task-... with the shared interpreter |
PermissionError on OneDrive |
Sync client holds the file | The runner retries; move large data with data_root |
16. Command reference
| Command | Purpose |
|---|---|
neqsim task-living <task> [--brief FILE] [--template production] |
Make a task living (never overwrites); production adds gates, constraints, guard and user_input.yaml |
neqsim task-note <task> [TEXT] [--lever N --lo X --hi Y \| --freeze N \| --constraint N --kpi K --limit X] [--setting K=V] [--by NAME] [--expires DATE] \| --list \| --resolve ID |
Add, list or resolve engineer comments and restrictions |
neqsim task-cycle <task> [--mode monitor\|solve] [--stages a,b] [--dry-run] [--no-agent] [--now ISO] |
Run one cycle |
neqsim task-solve <task> [--until goal\|converged] [--max-rounds N] [--no-agent] [--allow-unconfirmed] [--reset] |
Solve loop |
neqsim task-resume <task> [--max-rounds N] [--no-agent] |
Resume interrupted cycle/solve from persisted task state |
neqsim task-update <task> [--no-agent] [--dry-run] |
Detect file-level evidence changes, analyze impact and rerun only mapped stages (full cycle for unmapped changes) |
neqsim task-backtest <task> --start ISO --end ISO [--step-hours 24] [--name N] [--repeat] |
Replay archived data |
neqsim task-schedule <task> [--daily HH:MM] [--install\|--remove\|--show] |
Schedule monitor cycles |
neqsim task-promote <task> <cycle-id> --reviewer NAME [--note TEXT] |
Promote a cycle to the baseline |
neqsim task-ledger <task> [list\|show ID\|set ID STATUS --by NAME [--note TEXT]\|merge FILE] |
Improvement ledger |
neqsim task-status [task-or-folder] |
Status of one task, or of all living tasks in a folder (default: the task root) |
neqsim task-report <task> [--formal] |
Rebuild the Living Report (--formal also rebuilds the ordinary current-best Word/HTML report) |
neqsim task-report <task> --final --reviewer NAME [--note TEXT] [--pdf] |
Generate or reuse an immutable reviewed Final Report revision through the canonical report pipeline |
neqsim task-reference-case [parent-folder] |
Create the public reference task (default: in the task root) |
<task> is a folder path, or the name of a folder inside the task root.
17. Production optimisation: advisory loop with engineer input
For a plant model that should be kept current and used to recommend operating changes, create the task with
neqsim task-living <task> --template production. The cycle then runs:
sense -> evidence -> inputs -> script:model_update -> script:optimize -> kpis -> gates -> constraints -> guard
-> drift -> goal -> diff -> outcome -> ledger -> digest -> notify
The loop is advisory: nothing is written to the control system, and every accepted proposal is marked
requires_approval. An engineer decides in the ledger (task-ledger ... set ID accepted --by NAME).
17.1 What the stages decide
| Stage | Rule |
|---|---|
model_update (your script) |
Reads live data, updates and runs the model, returns KPIs and the residuals against the measurements |
gates |
gates: in the plan ({name, kpi, abs_max\|max\|min}). A failed or missing KPI blocks all advice (model_gate:<name>) |
constraints |
constraints: in goal.yaml ({name, kpi, op, limit, margin, warn, hard, source}). margin tightens the limit. A hard constraint with no limit is unconfirmed and blocks all advice |
optimize (your script) |
Searches the levers and returns proposals with setpoints, expected_gain, predicted KPIs, objective_kpi, baseline_value, and the fail-closed optimizer_evidence below; field studies may also return accepted_point_envelope_evidence |
guard |
Keeps a proposal only if its numeric data are finite, its evidence belongs to the current cycle and proves a fully validated/replayed candidate, it has a gain above production.min_gain, and every hard constraint is predicted inside its margin. When production.require_envelope_evidence is true, the guard also requires a complete accepted-point envelope bound exactly to the proposal’s action identifiers and values. guard.json lists the reasons for every withheld proposal |
outcome |
For ledger items in status implemented: realised gain against predicted gain (outcome_confirmed: / outcome_miss:) |
demonstrated_limit(values, quantile=0.99, design=...) gives an equipment limit from operating experience: the
larger of the design value and a high quantile of historian samples. Use it where equipment has been run above
its design value before; record who accepted that.
Every optimizer proposal must include this evidence from the same cycle. The guard requires each boolean to be
exactly true; missing, false, or stale evidence withholds the proposal rather than inferring success:
optimizer_evidence:
cycle_id: <ctx.cycle_id>
simulation_converged: true
candidate_feasible: true
candidate_finite: true
constraint_evidence_complete: true
state_restore_complete: true
accepted_point_replayed: true
actions_complete: true
evaluation_count: 42
runtime_seconds: 12.8
state_restore_complete covers restoration after every rejected/intermediate evaluation;
accepted_point_replayed confirms the selected point was then applied and re-simulated from a known state.
actions_complete must be false when any requested setpoint or routing action was rejected. The accepted ledger
item retains this block, including the positive integer evaluation count and finite non-negative runtime, so
resume and change-driven cycles preserve the incumbent’s validation and performance provenance.
For field-to-facility studies, set production.require_envelope_evidence: true and attach the map from
ProcessModelOperatingEnvelopeStudy.getAcceptedPointEnvelopeEvidence(...) as
accepted_point_envelope_evidence. The guard revalidates the persisted JSON instead of trusting its Java origin:
the cycle and optimizer block must match; ordered accepted_action_ids and
accepted_candidate_values must bind exactly to setpoints; slice study/evaluator identities and anchors must
match; ordered axis sets must be unique; every slice must be complete; every point must have restored and
reconverged the baseline; and point, evaluation and runtime totals must reconcile. Exploratory points may be
physically infeasible or fail candidate convergence—the envelope records those boundaries—but they cannot leave
the live model partially restored. The ordinary production template leaves this requirement disabled for backward
compatibility; a supplied envelope is always validated strictly. Accepted proposals retain the complete block in
the existing append-only ledger, so resume and evidence-change cycles do not depend on chat history or a second
state store.
Map every file that defines routing, constraints, limits, or model inputs to the production guard, including
mapped network pump/compressor power, common-shaft speed and power, driver limits, and shared host-power limits.
Declare the production-stage dependencies that must rebuild its evidence:
evidence:
rules:
- name: field-operating-definition
matches: [study_config.yaml, continuous/goal.yaml]
stages: [guard]
conclusions: [validated field operating point and envelope]
dependencies:
guard: ["script:optimize", gates, constraints]
"script:optimize": ["script:model_update"]
gates: ["script:model_update"]
constraints: ["script:model_update"]
neqsim task-update <task> then reruns the model, optimizer, constraints, and guard while retaining unrelated
cycle results. Map production and injection line-ups plus controlled reservoir-lifecycle boundaries and host-power
definitions through the same rule. The route selectors remain discrete process actions, while reservoir pressure
remains a model input synchronized through its owning WellFlow.ReservoirLayer. Read mapped network and standalone
rotating-equipment demand from the canonical ProcessModel.getPower(...) result and preserve the common-shaft,
driver, and shared-resource evidence; the task layer does not duplicate well, network, or rotating-equipment physics.
Non-finite mapped demand or a driver/shared-power breach produces no replacement proposal. Before any
model update, optimization, or guard runs, the evidence stage marks prior optimizer/envelope evidence in the ledger
evidence_current: false. Unfinished advice is superseded and cannot remain the current incumbent. A replacement
is a new proposed ledger item with revalidation_of, the changed evidence paths, the new cycle identity, and a
fresh envelope; this deliberately requires a new engineering review. If the revised limit or route leaves no safe
candidate, no replacement is created. Historical implemented records are retained for outcome traceability but
their validation evidence is still marked non-current. If a later operating definition becomes safe again, its new
item links to the most recent superseded item even when an intermediate cycle produced no proposal. Changes mapped
only to reporting stages do not invalidate
optimizer evidence. The Living Report exposes the evidence state for every ledger item. Unmapped changes and malformed
evidence_sensitive_stages configuration retain fail-closed invalidation behavior.
A malformed optimizer result or failed guard still leaves the old advice visibly stale, even when the guarded
ledger-publication stage is skipped. Retrying or resuming the same invalidation does not duplicate its events;
resuming after a replacement was already persisted preserves that cycle’s fresh evidence. Resume restores the
original optimizer proposals and reruns the inexpensive gates, constraints and guard; withheld proposals remain
withheld, and accepted proposals retain their approval requirement. It does not rerun the completed model or
optimizer. The ledger also requires optimizer/envelope proposals to match the exact content accepted by the
current cycle’s guard. This rejects custom plans that omit the guard, proposals inserted after the guard,
and changes to already guarded proposals. A dry run reports the
opportunities it would invalidate without changing the persisted ledger. The guard remains required before any
new proposal can be written.
Custom production plans must keep evidence before model update, optimization, and guard, as the production
template does. The ledger fallback alone cannot provide early invalidation when a failed guard skips publication.
17.2 Levers: every operator-adjustable parameter
Levers are listed under production.levers in the plan. A lever is any setting the operators can change:
separator pressures, heater and cooler outlet temperatures, valve pressures, and well choke opening.
The Snorre A reference task supports three kinds (see continuous/stages/optimize.py in that task):
| Kind | Fields | Note |
|---|---|---|
separator_pressure |
separator, step_bar, lo_barg, hi_barg |
Also sets the let-down valves into that separator |
well_rate |
manifold, step_pct, lo_pct, hi_pct |
Choke opening, modelled as a rate scale on the manifold feed at constant GOR and water cut and unchanged arrival pressure. The proposal carries needs_well_check because the well’s inflow and tubing limit are not in the model |
equipment_setpoint |
tag, setter, getter, unit, step, lo, hi |
Any unit-operation setter, e.g. an outlet temperature. Implemented but not yet run on a real model: check the getter and setter signatures (units) first |
Bounds are the operating-experience envelope; widen them only with the operator’s agreement (use a note, 17.3). Each lever is stepped both ways, then the positive moves are run together as one candidate, because separators and compressors share load and the gains do not add.
17.3 Comments and restrictions from the engineers
Write between cycles; the next cycle acts on it. Entries are in continuous/user_input.yaml:
entries:
- id: U-001
date: 2026-10-03
by: NAME
status: active # active | resolved
expires: 2026-11-01 # optional
text: "Vigdis HP wells: sand limit, do not open more than 5 %."
effects:
- {kind: lever_bound, lever: "Vigdis HP wells", hi: 5}
- {kind: lever_freeze, lever: "20D-VA60 3rd stage"}
- {kind: constraint, name: rvp_spec, kpi: export_rvp_bara, op: "<=", limit: 0.70, margin: 0.05}
- {kind: setting, key: production.rvp_bias_bara, value: 0.05}
- {kind: gate, name: separator_T_residual, abs_max: 4.0}
| Effect | Does |
|---|---|
lever_bound |
Replaces the plan bounds of a lever (narrows or widens). Scripts call lever_limits(ctx, name, lo, hi) |
lever_freeze |
Keeps the lever at its current value |
constraint |
Sets or adds a goal constraint; setting the limit confirms an unset one |
setting |
Sets a dotted plan key |
gate |
Changes a model-validity gate |
An entry without effects is free text: it is printed in the digest and the living report and goes to the agent
with the digest. A new or changed entry raises user_input:<id> once; an expired one raises
user_input_expired:<id>. Set status: resolved (or neqsim task-note <task> --resolve U-001 --by NAME) to stop
its effects. Keep resolved entries: the file is the record of who restricted what and when.
neqsim task-note <task> "Sand limit" --lever "Vigdis HP wells" --hi 5 --by NAME
neqsim task-note <task> "Spec from lab" --constraint rvp_spec --kpi export_rvp_bara --limit 0.70 --margin 0.05
neqsim task-note <task> --list
17.4 Process boundary conditions
Boundary conditions that the operators do not control, such as the gas export or gas injection pressure, are read
each cycle as KPIs from production.gas_tags ({kpi_name: historian tag}, 24 h means). They appear as
<name>_meas; where the model has the equipment, <name>_model and <name>_resid show the difference, so a
model that has drifted from its boundary shows up. Add a constraint on a boundary KPI to enforce a limit.
17.5 What the loop does not know
State these in the task README so the reviewer sees them: well-rate moves assume the wells can deliver; equipment
limits are checked only where a constraint exists (needs_equipment_check otherwise); a calculated product spec
such as RVP needs the lab bias; and a slow model limits how often the loop can run.
Related documentation
- Task Solving Guide — the one-off task workflow this builds on
- Skill:
.github/skills/neqsim-continuous-task-improvement/SKILL.md - Agent:
.github/agents/continuous-improvement.agent.md - Runner source:
devtools/neqsim_continuous/