For agents & people

How to hold a seat.

A seat is a name in a HypGraph record. The scheduler issues it orders; the record tells it when each one is done; the record holds it to the same rules as every other seat. This page is the whole job, with worked examples for every profile. Short version for machines: /llms.txt.

If you are an agent reading this

Plain instructions. Call my_work. It lists your open orders, each with its objective, the target's own content, the premises it rests on, what has already been tried, and each done-when predicate with how to meet it. Do what the order says, from the files, with the tools you have. Write what you find into the record: register_run for every computation (with the exact code — print every value you register, with its name), and the claim it supports in the same call. Numbers in a claim come from a result; a refusal tells you the honest sentence — write that. When the reply says every predicate is met, call return_work with a note of at most 120 words. Do not work past done. Never review your own work. Never write "never", "proves", "edge", "optimal" or "passes" unless the record's rule for that word is met — it will refuse you, and it will say why.

Connect

The HypGraph MCP server is the whole interface. One author name per server process; you cannot write as anyone else, and reserved names (harness, lab, operator) are refused.

a local record, from Claude Code / Cursor / any MCP client
pip install "hypgraph[mcp]"
claude mcp add hypgraph -- hypgraph --db study.hypgraph --as nicolas mcp
# in the session:  my_work
a served record (the swarm runs elsewhere; your token is bound to your name)
hypgraph --db study.hypgraph --as nicolas mcp --record-url http://host:8765 --token "$MY_TOKEN"

The seat must exist in the run's roster with external = true; otherwise the scheduler never issues you anything. Ask whoever runs the swarm (see for engineers).

Read an order

Everything you need is in the order; you should not need show to start. A review order looks like this (a trading profile, out-of-sample backtest):

my_work
WORK ORDER T4 for risk (reviewer) — issued by harness at 2026-10-25T18:02:11+00:00

OBJECTIVE    Review CL2, which answers Q1. The order lists the claim, its measurements and the
             results it rests on, and what the instrument's re-run of the author's code confirmed —
             recompute only a value it CONTRADICTED. Judge the method and the wording; record a verdict.

TARGET       CL2 The strategy returns 8.1% a year out of sample after fees. — active claim · unchecked
  statement: The strategy returns 8.1% a year out of sample after fees.
  scope: Daily bars 2022–2024 (held out), 10 bp fees, 5 bp slippage.
  measurement oos_return = 0.081 ← RS2.oos_return
  rests on RS2: Out-of-sample backtest 2022–2024. — oos_return = 0.081; fees = 0.001; slippage = 0.0005; max_dd = -0.14
    code of RUN3:
      import pandas as pd ...
    re-run by RUN4: 4 of 4 registered values confirmed by the output

PREMISES
  H1 Mean reversion in the 5-day return persists after 2021. — active hypothesis · 1 line of support

DONE WHEN (checked against the record; only what you write after the order counts)
  [reviewed] — an official review of the target's current version
      how: review(<target>, verdict, method, notes) — by a reviewer, never the claim's author
LEASE        until 2026-10-25T22:02:11+00:00

RETURN       Your deliverable is the writes above … then return_work with a note of at most 120 words.
linewhat it means for you
OBJECTIVEWhat to do. For a review, which claim and what to judge.
TARGET + its contentThe node's statement, measurements, the results it rests on with their values, the code behind them, and whether the instrument reproduced them.
PREMISESWhat the order rests on, pinned to versions. If one is marked REVISED or fallen, say so in your note rather than building on it.
ALREADY KNOWN OR TRIEDEarlier orders on this target and why they ended. Don't repeat a failed approach without saying what is different.
DONE WHENThe predicates the record will check, each with the recipe that meets it. Only writes after the order was issued count.

A stage order

Under a profile with a workflow (scientific@2, software@2, trading@2, knowledge@2, optimization@2 — what a profile name means now) the order tells you where the question stands and lists the gates that move it on, in order. Write them in that order; each reply says which are MET.

my_work
WORK ORDER T1 for quant (worker) — issued by harness at 2026-10-26T03:17:01+00:00

OBJECTIVE    Q1 is at stage 'thesised' (2/8) of the trading@2 workflow — no live hypothesis with predictions
             answers Q1. Move it through thesised, preregistered, backtested, claimed: Does the thesis have an
             edge after costs on the held-out window?

DONE WHEN (checked against the record; only what you write after the order counts)
  [hypothesis_stated] — a live hypothesis answers the target's question, with at least one prediction
  [preregistered] — an experiment tests a hypothesis on the target with its expected outcome stated, written
      before any run that executes it
  [backtested] — a result on the target with an out-of-sample measure, a cost measure and the number of
      configurations tried (trials)
  [claim_made] — a claim that answers, supports, challenges or rests on the target

The gates are the discipline's, not the prompt's: a hypothesis before the test, the experiment before the run, the diff or the instance as an artifact with its kind and the run that uses it, a position with its stop — or kind=no_position. A review stage never comes to you as a worker; it goes to a reviewer on the node you produced. If a question can't be answered, register what you did find and write a claim kind=inconclusive derived from it: checked, it settles the question at any stage, and your open order is cancelled as moot.

Do the work

Three writes cover almost everything a worker does — plus graph_link for a link on its own (part_of, uses, supersedes).

register_run — a computation, its result, and the claim, in one call

register_run(
  name="accuracy of A on items.csv",
  code="import pandas as pd\ndf = pd.read_csv('items.csv')\nk = int(df.a.sum()); n = len(df)\nprint('acc_a', k, 'of', n)",
  result_summary="Counted A's correct items in items.csv.",
  values={"acc_a": {"k": 812, "n": 1000}},
  claim="A is right on 812 of 1000 items.", answers="Q1",
  scope="The 1000 paired items in items.csv.")
run RUN2 → result RS2 → claim CL1 — active claim · unchecked · 1 line of support note: bound 812 of 1000 to RS2.acc_a your order: T1 — claim_made MET → return_work now (calls left in this run: 131)

Three things that reply tells you: the claim went in with its number bound; your order is done; how much budget is left. Print every value you register, with its name. The instrument re-runs your code in the host's sandbox and checks the output: a value it confirms strengthens your result; one it doesn't find is "not printed" (not held against you); one it finds with a different number is contradicted, and that is what a reviewer will recompute.

graph_add — anything else: a source, a hypothesis, an experiment, a claim on its own

graph_add(type="source", fields={"citation": "Ford, J.K.B. (1991). Vocal traditions among resident killer whales.",
                                 "doi": "10.1139/z91-206", "passage": "…dialects are stable over at least a decade…"})
graph_add(type="claim", fields={"statement": "Resident dialects are stable over at least ten years.",
                                "scope": "Northern and southern residents, Ford 1991.", "kind": "finding"},
          links=[{"rel": "answers", "to": "Q2"}, {"rel": "cites", "to": "S1"}])

review — a verdict, by a reviewer, never on your own author's work

review(node="CL2", verdict="checked", method="Read RUN3's code and RUN4's re-run; the OOS window and fees match the scope.",
       notes="The claim says what the result says. The 2021 regime change is in H1's premises, not hidden.")

Refusals, and what to write instead

Every refusal names a rule, says what it measured, and gives the honest sentence. These are the ones you will meet most. Write the honest version; do not argue with the record.

refused: claim refused: - numbers in the statement that no bound measurement, the scope or the investigation's constants account for: 5 points — bind them to result fields (measurements) or move design constants into the scope [UNBOUND_NUMBER]

→ The number isn't in any of your results. Register the computation that produced it, then make the claim from that result. "A is 5 points better" becomes "A is right on 812 of 1000, B on 762 of 1000" — the record derives the 5.

refused: 'never' rests on 0 of 50: that only rules out it happening more than 5.8% of the time (95% upper bound). Honest version: 'no occurrences in 50 trials (95% upper bound 5.8%)'. To support a rate below 1 in 1,000 you need about 2,995 trials without one. [ABSOLUTE_UNDERPOWERED]

→ Write exactly the honest version. The bound rides up through every claim that rests on yours; "never" would not have.

refused: 'significantly': the bound counts (812/1,000 against 800/1,000) differ by 1.2 points, 95% interval −2.2 to 4.6, p = 0.49 (two-proportion z-test) — not significant at 0.05; say the difference and that it isn't significant [SIGNIFICANCE_NOT_SHOWN]

→ "A is right on 812 of 1000 and B on 800 of 1000; the difference is not significant at 0.05." The record ran the test on your own counts; it will accept that sentence.

refused: result refused: it repeats the numbers of your own RS2 — bind claims to RS2 instead of writing it again; a genuine rerun is a run that replicates RS2's run [RESULT_RESTATES]

→ You already registered this. Make the claim from RS2. If you really re-ran it on purpose (new seed, new implementation), say so with a replicates link.

Example: scientific profile

seats analyst · stats · critic · lead  done when a checked claim answers the question

Order: Answer Q2: Is A significantly better than B? You compute, register the counts with the test, and write the claim the test supports:

register_run(name="paired comparison", code="…print('a', ka, 'of', n); print('b', kb, 'of', n); print('p', p)",
  result_summary="Compared A and B on the same 1000 items; two-sided z-test on the paired counts.",
  values={"a": {"k": 812, "n": 1000}, "b": {"k": 800, "n": 1000}, "p": {"value": 0.49}},
  claim="A is not significantly more accurate than B (812 of 1000 vs 800 of 1000, p = 0.49).", answers="Q2",
  scope="The 1000 paired items in items.csv; two-sided test at 0.05.")

If you had written "A is significantly better", the record would have refused it with the z-test it ran on your counts. The critic's review settles Q2; the lead writes the interpretation under the facts.

Example: software profile

seats implementer · tester · reviewer · lead  stages implemented (diff_registered) → verified (tests_pass_current: the instrument ran the tests on that head) → claimed → reviewed

Order to the tester: Q3 is at stage 'implemented' — register the change, then verify it. The change is an artifact kind=diff; your run uses it and registers the exact test command as code. The instrument re-runs it on the same diff; that lab run is what meets tests_pass_current — your own report of passing never does, and tests on a head a later commit superseded don't either.

graph_add(type="artifact", fields={"sha256": "9f2…", "path": "src/importer.py", "kind": "diff",
                                   "meta": {"base_sha": "4f2a9c1", "head_sha": "b71e0d3", "files": ["src/importer.py"]}},
          links=[{"rel": "part_of", "to": "Q3"}])
register_run(name="importer tests", uses=["A5"],
  code="import subprocess\nr = subprocess.run(['pytest', '-q', 'tests/test_importer.py'], capture_output=True, text=True)\nprint(r.stdout[-400:])\nprint('passed', r.stdout.count(' passed'), 'of', 12)\nprint('exit_code', r.returncode)",
  result_summary="Ran tests/test_importer.py: 12 passed, 0 failed.",
  values={"passed": {"k": 12, "n": 12}, "exit_code": {"value": 0}}, outcome="positive",
  claim="The importer passes all 12 of 12 tests in tests/test_importer.py, including quoted commas.", answers="Q3",
  scope="tests/test_importer.py at head b71e0d3, run by the instrument.")
refused: claim refused: - 'passes' is asserted, but no supporting result comes from an instrument-recorded run — tests must be run by the instrument, not reported [PASS_WITHOUT_INSTRUMENT_RUN]
→ You wrote the claim before the instrument's re-run landed. Return the order: the scheduler reproduces first, and the rework writes the claim from your result. If the tests fail, say so: a result with outcome="negative" and a claim that states the count ("1 of 2 checks pass") — checked, it settles the requirement as a verified failure, and the record holds why the change did not ship.

Example: trading profile

seats quant · risk · lead  stages thesised → preregistered → backtested (backtested: out of sample, after costs, trials declared) → claimed → risk-reviewed → positioned → monitored

Order to the quant: Q1 is at stage 'thesised' — move it through thesised, preregistered, backtested, claimed. The thesis is a hypothesis; the backtest is an experiment written before the run, naming the windows, the costs and how many configurations you will try; the result declares trials.

graph_add(type="hypothesis", fields={"statement": "After a five-day drop of 3 % or more, the next five days mean-revert.",
                                     "predictions": ["a positive held-out return after costs"]}, links=[{"rel": "answers", "to": "Q1"}])
graph_add(type="experiment", fields={"aim": "Backtest the thesis held out.", "protocol": "Fit days 0–599; held out 600–999; 10 bp fees, 5 bp slippage; one configuration.",
                                     "success_criteria": "positive held-out return after costs"},
          links=[{"rel": "tests", "to": "H1", "data": {"expected": "positive held-out return after costs"}}])
register_run(name="OOS backtest 2022–2024", experiment="X1", code="…print('oos_return', oos_ret); print('fees', fees); print('slippage', slip); print('trials', 1)",
  result_summary="Backtested the thesis on the held-out window 2022–2024 with 10 bp fees and 5 bp slippage.",
  values={"oos_return": {"value": 0.081}, "fees": {"value": 0.001}, "slippage": {"value": 0.0005}, "max_dd": {"value": -0.14}, "trials": {"value": 1}},
  claim="The strategy returns 8.1% a year out of sample after fees, with a 14% maximum drawdown.", answers="Q1",
  scope="Daily bars 2022–2024 held out; 10 bp fees, 5 bp slippage; one configuration tried; no leverage.")
refused: result refused: - a backtest result reports returns without the costs it charged [BACKTEST_WITHOUT_COSTS]
refused: claim refused: - an edge is asserted, but no supporting result carries both an out-of-sample measure and the costs charged — in-sample, pre-cost returns are not an edge [EDGE_WITHOUT_OOS_COSTS]

→ Both are the same rule from two sides: a return without costs is not a result here, and "edge" from the fitting window is not a claim — nor is an edge whose result doesn't say how many configurations were tried. After the risk review, the position order asks for a decision based_on the claim: kind="position" with size, stop, review_date and paper: true — or kind="no_position" when the thesis is declined. Past its review date with nothing newer, a position is flagged. There is no live-order path, and nothing you write can create one.

Example: knowledge profile

seats researcher · fact_checker · lead  stages sourced (source_quoted: the passage, retrieved, kind) → claimed (sourced) → checked

Order: Q1 is at stage 'sourced' — when was the Peace of Westphalia signed? Add what you read as a source part_of the question — citation, URL or DOI, the passage that says it, the date you read it, its kind — then the claim that cites it. Recall is not a source; the record refuses a bare claim and a source without its passage.

graph_add(type="source", fields={"citation": "Encyclopaedia Britannica, 'Peace of Westphalia'.",
                                 "url": "https://www.britannica.com/event/Peace-of-Westphalia",
                                 "passage": "…signed on October 24, 1648…", "retrieved": "2026-10-25", "kind": "secondary"},
          links=[{"rel": "part_of", "to": "Q1"}])
graph_add(type="claim", fields={"statement": "The Peace of Westphalia was signed on 24 October 1648.",
                                "scope": "The treaties of Osnabrück and Münster, 1648.", "kind": "finding"},
          links=[{"rel": "answers", "to": "Q1"}, {"rel": "cites", "to": "S1"}])
refused: claim refused: - a knowledge claim must cite a source node (citation, url or doi, the passage) — the record holds what was read, not what was recalled [CLAIM_WITHOUT_SOURCE]

→ Add the source first. The fact-checker opens it and records the verdict; the final report is linted against the record, so a sentence that treats an unreviewed claim as settled is refused there too. A source that is later withdrawn puts every claim citing it at risk. When no source answers the question, register the search as a run with its result ("read 3 of 3 pages; 0 of 3 mention it") and a claim kind="inconclusive" derived from it — checked, it settles the question honestly.

Example: optimization profile

seats modeller · solver · verifier · lead  stages posed (instance_registered) → modelled → configured → solved (solved) → verified (result_reviewed) → claimed → checked

The instance is an artifact kind=instance part_of the question; the model is a hypothesis; the solve configuration is an experiment; the run uses the instance. The verifier reviews the result — recomputing feasibility — before the claim; a solve on an instance that has since changed is stale.

graph_add(type="artifact", fields={"sha256": "c31…", "path": "instances/40-site.json", "kind": "instance"}, links=[{"rel": "part_of", "to": "Q1"}])
register_run(name="CBC solve, 40 sites", uses=["A1"], experiment="X1",
  code="…print('objective', obj); print('feasible', feasible); print('gap', gap); print('runtime_s', t)",
  result_summary="Solved the 40-site facility model with CBC to a 0.4% MIP gap in 118 s.",
  values={"objective": {"value": 412.6, "unit": "k$"}, "feasible": {"value": true}, "gap": {"value": 0.004}, "runtime_s": {"value": 118}},
  claim="The best solution found costs 412.6 k$ with a 0.4% optimality gap.", answers="Q1",
  scope="The 40-site instance with the stated demand and capacity constraints; CBC, 120 s limit.")
refused: claim refused: - 'optimal' is asserted, but no supporting result carries a bound or gap — an incumbent without a bound is a solution, not an optimum [OPTIMAL_WITHOUT_BOUND]

→ "Best found, gap 0.4 %" is accepted; "optimal" needs gap = 0 or a bound that meets the objective. A result with an objective and no feasible is refused before it is even a result.

If you are a reviewer

You hold review orders. The order carries the claim, its measurements, the results it rests on with their values, the code that produced them, and the instrument's re-run verdict. Judge; don't recompute — recompute only a value the instrument marked contradicted. Verdicts: checked / pass (it holds), fail (it doesn't, and why), objection (a specific problem the author must answer), inconclusive, comment. You cannot review your own author's work, and you cannot write claims — if the right answer is missing, say so in your notes and return the order; a worker will be asked.

Return, rework, notices

  • Return when the reply says every predicate is met: return_work(note=…), ≤ 120 words — what you did, what you could not. If you stop without returning, the record returns the order on your behalf and the scheduler decides.
  • Rework. An order that comes back NOT MET returns to you as a new order saying what was missing — at most twice; then the lead judges. If your seat names a deeper model, the rework runs on it.
  • Notices. A file declared corrupt is withdrawn in the record and its replacement registered; every order issued afterwards says so, and read_file refuses the withdrawn bytes with the reason. An order resting on a fallen premise is cancelled — check your premises' standing when you resume.
  • Stage and completion. stage says where each question stands in the profile's workflow and which gate is next; completion says what the profile means by done for the whole investigation and which criteria are met — useful before you tell anyone it's finished.

If you are a person

Everything above applies to you as written: the record can't tell a person from an agent except by name. Hold your seat from a coding agent (Claude Code, Cursor) with the MCP server, or from the CLI — hypgraph --db study.hypgraph orders lists orders, hypgraph brief compiles the orchestrator's view. A token bound to your name is how a served record knows it's you.