Skip to content

FE-1270: Extract Python bindings for the Petrinaut CLI into @local/petrinaut-python - #9228

Open
kube wants to merge 1 commit into
mainfrom
cf/fe-1270-create-python-bindings-to-petrinaut-core
Open

FE-1270: Extract Python bindings for the Petrinaut CLI into @local/petrinaut-python#9228
kube wants to merge 1 commit into
mainfrom
cf/fe-1270-create-python-bindings-to-petrinaut-core

Conversation

@kube

@kube kube commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Using Petrinaut from Python meant copying a subprocess wrapper out of the CLI's docs. apps/petrinaut-opt carried its own client, which covered two of the CLI's five protocol methods and was private to that service. This PR extracts a package both can use.

Stack #9226: FE-1410, FE-1411, FE-1408, and FE-1413 have merged, so this PR now sits on main and is the bottom of the stack. FE-1412 (#9229) is above it.

🔗 Related links

🔍 What does this change?

New package libs/@local/petrinaut-python (import name petrinaut), stdlib-only and POSIX-only, internal to the monorepo:

  • PetrinautSession serves one model per process, from a file or over stdin, and exposes healthz(), metadata(), run(), and a request() method for any protocol method.
  • OptimizationSession drives a study over a manifest the bindings treat as opaque JSON: describe_optimization(), objective(parameter_values), and evaluate(parameter_values). evaluate returns the full result, so it carries replicates whenever the manifest sets execution.seedsPerTrial above 1.
  • Five exported names: PetrinautSession, OptimizationSession, PetrinautClientError, PetrinautProtocolError, PetrinautRunError.
  • The process handling comes from petrinaut-opt's client with one change: the read loop's three copies of the line-cap and UTF-8 decode blocks collapse into one local helper. A scrubbed child environment, bounded reads with deadlines, and process-group shutdown all behave as before.

petrinaut-opt no longer knows what it is calling

The service depends on the bindings and nothing else. It never names the CLI, in code, tests, docs, configuration, or the image, so replacing what the bindings spawn touches only the bindings.

  • Its 15 files under apps/petrinaut-opt lose 928 lines and gain 132. The deleted client and its test are 775 of those, and the rest is the CLI leaving the service's vocabulary: comments, docstrings, two response detail strings, and the architecture-docs @role.
  • create_model() is inlined into its only caller. Its docstring called it a seam for API tests, but every test patches initialize_optimizer instead, so nothing used it.
  • The README drops from 246 lines to 203. Its CLI protocol section documented the JSON-lines exchange, which the bindings own and the CLI's own manual covers; what remains describes the four session calls this service makes and what it does with the description.
  • @hashintel/petrinaut-cli moves from the bindings' devDependencies to their dependencies, which is what it always was: they spawn it. That is what lets the image stop naming it.
  • The image now provisions the bindings' runtime rather than a named package: it prunes and focuses on @local/petrinaut-python, builds --filter '@local/petrinaut-python...', and takes the petrinaut executable from the closure's own node_modules/.bin. The bindings give their child a fixed PATH, so it is linked into /usr/local/bin.
  • uv sync --no-editable builds the bindings into the virtualenv, so the runner copies the virtualenv alone. The editable install's requirement that the source sit at a matching absolute path in two stages is gone.
  • Two variables become one. The image's own NODE_OPTIONS never reached the child, because the bindings scrub the environment, and the passthrough is renamed PETRINAUT_CHILD_NODE_OPTIONS so it names the bindings' child rather than a package.
  • Enforcement arrives one layer up, not here: FE-1414 (FE-1414: Support Python packages in the architecture docs #9230) registers both Python packages with the architecture docs and adds the import extraction they need. From that layer the generated model shows the optimizer layer with one edge, optimizer → python-bindings, and none to cli. At this layer the decoupling is a code fact, not yet a checked one.

Review pass

  • A failure inside initialize_optimizer now closes with graceful=False, which is what the bindings' own close() contract requires of failure paths. It previously waited out the graceful EOF while holding the admission slot.
  • Request encoding moved above the exchange. A params value that cannot be serialized is the caller's bug, and used to force-close a healthy session and surface as a transport failure; TypeError escaped the documented exception types entirely.
  • A read deadline or a size cap now keeps its own message instead of collapsing into failed to communicate with the Petrinaut CLI, and other transport failures name the underlying error type. A stalled run and a broken pipe were previously indistinguishable in the logs.
  • OptimizationSession.from_model_file(...) used to raise ValueError: provide exactly one of optimization_manifest or manifest_path, because the inherited constructor routed through cls. The model constructors now return a PetrinautSession.
  • The image ships built output only. COPY /repo/libs was carrying 227 petrinaut-core source files, the CLI's sources and test fixtures, and the bindings' tests into /opt/petrinaut-runtime, all readable to the sandboxed child that evaluates user-authored metric expressions.
  • Removals: the redundant source_label arguments, three hardcoded 8 MiB strings that could drift from the constants they describe, two section banners, unread terminated/killed flags on the test double, one tautological test, and duplicated prose across the two READMEs. The bindings README now names the timeout and cap constants rather than quoting their values, states that a session serves one request at a time, and says how to put the executable on PATH.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies a workspace but not a publishable library. @local/petrinaut-python is private and consumed inside the monorepo.

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR
    • The package README and libs/@hashintel/petrinaut-cli/README.md. The CLI usage manual keeps the stdlib wrapper it gained in FE-1413; FE-1456 points it at the bindings once their own manual exists. In-app user-guide behaviour is unchanged.

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • affected the execution graph, and the turbo.json's have been updated to reflect this
    • The bindings' test:unit depends on the CLI's build, since the end-to-end tests spawn the bundle. petrinaut-opt's codegen inputs include the bindings sources, narrowed to *.py so bytecode does not change the cache key.

🛡 What tests cover this?

  • Bindings: 23 tests. Bootstrap behaviour for from_model_file and from_model, healthz/metadata/run, error frames and protocol errors, timeouts and shutdown, and the optimizer client suite that moved here. Every test from the deleted test_petrinaut_client.py is present; the one dropped test asserted that two private attributes equalled the constants they default to.
  • Two of those spawn the built CLI end to end: one PetrinautSession run of the SIR model, and one OptimizationSession study over the checked-in supply-chain manifest. They skip when the bundle or node is missing. FE-1412 adds a seeded study.
  • apps/petrinaut-opt: 75 tests pass against the package.

❓ How to test this?

  1. turbo run test:unit --filter @local/petrinaut-python
  2. cd apps/petrinaut-opt && uv sync && uv run pytest
  3. uv run python -c "from petrinaut import OptimizationSession, PetrinautSession"

🐾 Next steps

FE-1412 scales the client's per-response deadline by the seeds a study describes.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 20, 2026 10:54am
hashdotdesign-tokens Ready Ready Preview Aug 20, 2026 10:54am
petrinaut Ready Ready Preview Aug 20, 2026 10:54am
petrinaut-docs Ready Ready Preview Aug 20, 2026 10:54am

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team type/eng > backend Owned by the @backend team area/tests New or updated tests area/apps type/legal Owned by the @legal team labels Aug 16, 2026
@kube kube self-assigned this Aug 16, 2026
@kube kube changed the title FE-1270: Extract Python bindings for the Petrinaut CLI FE-1270: Extract Python bindings for the Petrinaut CLI into @local/petrinaut-python Aug 16, 2026
@kube
kube deployed to pull-request August 16, 2026 18:06 — with GitHub Actions Active
@kube
kube deployed to pull-request August 16, 2026 18:06 — with GitHub Actions Active
@codspeed-hq

codspeed-hq Bot commented Aug 16, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 98 untouched benchmarks


Comparing cf/fe-1270-create-python-bindings-to-petrinaut-core (562ceaf) with main (760311c)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (afa2072) during the generation of this report, so 760311c was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@kube
kube force-pushed the cf/fe-1270-create-python-bindings-to-petrinaut-core branch from c1e9f74 to a2a4545 Compare August 17, 2026 23:51
@kube
kube deployed to pull-request August 17, 2026 23:52 — with GitHub Actions Active
@kube
kube deployed to pull-request August 17, 2026 23:52 — with GitHub Actions Active
@kube
kube force-pushed the cf/fe-1270-create-python-bindings-to-petrinaut-core branch from a2a4545 to cfbbb60 Compare August 17, 2026 23:56
@kube
kube marked this pull request as ready for review August 17, 2026 23:56
@kube
kube requested a review from a team as a code owner August 17, 2026 23:56
Copilot AI balanced review requested due to automatic review settings August 17, 2026 23:56
@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR Summary

Cursor Bugbot is generating a summary for commit cfbbb60. Configure here.

@kube
kube force-pushed the cf/fe-1270-create-python-bindings-to-petrinaut-core branch from 8b047b8 to 7176be8 Compare August 18, 2026 02:01
@kube
kube deployed to pull-request August 18, 2026 02:02 — with GitHub Actions Active
@kube
kube deployed to pull-request August 18, 2026 02:02 — with GitHub Actions Active
@kube
kube deployed to pull-request August 18, 2026 11:08 — with GitHub Actions Active
@kube
kube deployed to pull-request August 18, 2026 11:08 — with GitHub Actions Active
@kube
kube force-pushed the cf/fe-1270-create-python-bindings-to-petrinaut-core branch from 7176be8 to 14efab7 Compare August 18, 2026 15:18
@kube
kube deployed to pull-request August 18, 2026 15:19 — with GitHub Actions Active
@kube
kube deployed to pull-request August 18, 2026 15:19 — with GitHub Actions Active
@kube
kube force-pushed the cf/fe-1270-create-python-bindings-to-petrinaut-core branch from 14efab7 to 4fe418d Compare August 18, 2026 22:48
@kube
kube deployed to pull-request August 18, 2026 22:49 — with GitHub Actions Active
@kube
kube deployed to pull-request August 18, 2026 22:49 — with GitHub Actions Active

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 26 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 31 changed files in this pull request and generated no new comments.

Suppressed comments (1)

libs/@hashintel/petrinaut-cli/README.md:15

  • The architecture check described by this PR does not currently cover this new relationship. libs/@local/petrinaut-arch-docs/architecture.config.ts:34-57 registers only core, editor, and CLI; content/index.mdx:78-80 still says apps/petrinaut-opt is outside the generated model; and libs/@hashintel/petrinaut-cli/src/README.md:9-12 still says the optimizer directly spawns the CLI. Consequently the generated docs remain stale and no optimizer → python-bindings edge is enforced. Register the optimizer and bindings with layer declarations and update the stale architecture prose as part of this extraction.
The [`@local/petrinaut-python`](../../@local/petrinaut-python/README.md)
bindings drive this CLI from Python: sessions, run requests, and optimization
studies.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 31 changed files in this pull request and generated no new comments.

Suppressed comments (1)

libs/@local/petrinaut-python/src/petrinaut/optimization.py:14

  • The stated architecture check cannot currently cover this new boundary: libs/@local/petrinaut-arch-docs/architecture.config.ts:35-57 registers only the core, editor, and CLI packages, while src/extract.ts:158-165 explicitly rejects Python packages because no extractor exists. As a result, the claimed optimizer → python-bindings edge is never generated or enforced, so a direct optimizer-to-CLI dependency would pass the architecture check. Add Python extraction/registration for both this package and apps/petrinaut-opt (with their layer declarations and graph wiring), or remove the claim and track that enforcement separately.
class OptimizationSession(PetrinautSession):

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$26.1 \mathrm{ms} \pm 178 \mathrm{μs}\left({\color{gray}-0.960 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.39 \mathrm{ms} \pm 18.4 \mathrm{μs}\left({\color{gray}-4.153 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$12.1 \mathrm{ms} \pm 90.8 \mathrm{μs}\left({\color{gray}-3.012 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$42.6 \mathrm{ms} \pm 387 \mathrm{μs}\left({\color{gray}-0.643 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$14.2 \mathrm{ms} \pm 93.3 \mathrm{μs}\left({\color{lightgreen}-9.192 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$23.4 \mathrm{ms} \pm 206 \mathrm{μs}\left({\color{gray}-4.211 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$27.4 \mathrm{ms} \pm 208 \mathrm{μs}\left({\color{gray}1.10 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.74 \mathrm{ms} \pm 21.9 \mathrm{μs}\left({\color{gray}-3.484 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$13.8 \mathrm{ms} \pm 91.5 \mathrm{μs}\left({\color{gray}-3.765 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.68 \mathrm{ms} \pm 20.9 \mathrm{μs}\left({\color{gray}-1.844 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.97 \mathrm{ms} \pm 17.3 \mathrm{μs}\left({\color{gray}-1.084 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.34 \mathrm{ms} \pm 23.2 \mathrm{μs}\left({\color{gray}-0.528 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.04 \mathrm{ms} \pm 29.0 \mathrm{μs}\left({\color{gray}-0.497 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.57 \mathrm{ms} \pm 18.0 \mathrm{μs}\left({\color{gray}0.936 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.09 \mathrm{ms} \pm 20.8 \mathrm{μs}\left({\color{gray}0.091 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.30 \mathrm{ms} \pm 26.5 \mathrm{μs}\left({\color{gray}-0.544 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.42 \mathrm{ms} \pm 16.3 \mathrm{μs}\left({\color{gray}-2.211 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.05 \mathrm{ms} \pm 41.3 \mathrm{μs}\left({\color{gray}-0.807 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.63 \mathrm{ms} \pm 16.9 \mathrm{μs}\left({\color{gray}-1.457 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.52 \mathrm{ms} \pm 15.0 \mathrm{μs}\left({\color{gray}0.823 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.60 \mathrm{ms} \pm 14.4 \mathrm{μs}\left({\color{gray}-2.483 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$2.87 \mathrm{ms} \pm 17.9 \mathrm{μs}\left({\color{gray}-2.444 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.68 \mathrm{ms} \pm 16.1 \mathrm{μs}\left({\color{gray}-1.440 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.85 \mathrm{ms} \pm 13.8 \mathrm{μs}\left({\color{gray}-3.182 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.00 \mathrm{ms} \pm 15.3 \mathrm{μs}\left({\color{gray}0.110 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.71 \mathrm{ms} \pm 12.2 \mathrm{μs}\left({\color{gray}-1.768 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$2.98 \mathrm{ms} \pm 18.4 \mathrm{μs}\left({\color{gray}0.669 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.38 \mathrm{ms} \pm 20.6 \mathrm{μs}\left({\color{gray}-0.700 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$2.99 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}1.28 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.30 \mathrm{ms} \pm 17.6 \mathrm{μs}\left({\color{gray}0.544 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.33 \mathrm{ms} \pm 25.3 \mathrm{μs}\left({\color{gray}-0.315 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.98 \mathrm{ms} \pm 16.3 \mathrm{μs}\left({\color{gray}1.15 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.34 \mathrm{ms} \pm 25.5 \mathrm{μs}\left({\color{gray}1.63 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$43.0 \mathrm{ms} \pm 213 \mathrm{μs}\left({\color{gray}1.44 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$34.4 \mathrm{ms} \pm 191 \mathrm{μs}\left({\color{gray}1.00 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$36.4 \mathrm{ms} \pm 210 \mathrm{μs}\left({\color{gray}-0.761 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$33.4 \mathrm{ms} \pm 172 \mathrm{μs}\left({\color{lightgreen}-26.870 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$43.7 \mathrm{ms} \pm 248 \mathrm{μs}\left({\color{gray}-0.016 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$50.4 \mathrm{ms} \pm 284 \mathrm{μs}\left({\color{gray}0.633 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$40.1 \mathrm{ms} \pm 239 \mathrm{μs}\left({\color{gray}-1.133 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$92.4 \mathrm{ms} \pm 528 \mathrm{μs}\left({\color{gray}1.51 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$34.2 \mathrm{ms} \pm 167 \mathrm{μs}\left({\color{gray}-0.107 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$249 \mathrm{ms} \pm 840 \mathrm{μs}\left({\color{lightgreen}-10.492 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$10.8 \mathrm{ms} \pm 57.7 \mathrm{μs}\left({\color{gray}0.661 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$10.9 \mathrm{ms} \pm 50.9 \mathrm{μs}\left({\color{gray}0.104 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$10.9 \mathrm{ms} \pm 57.7 \mathrm{μs}\left({\color{gray}-1.058 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$10.9 \mathrm{ms} \pm 71.7 \mathrm{μs}\left({\color{gray}1.23 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$10.8 \mathrm{ms} \pm 51.6 \mathrm{μs}\left({\color{gray}-2.091 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$10.8 \mathrm{ms} \pm 62.3 \mathrm{μs}\left({\color{gray}-1.352 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$11.0 \mathrm{ms} \pm 55.5 \mathrm{μs}\left({\color{gray}-2.346 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$10.9 \mathrm{ms} \pm 68.5 \mathrm{μs}\left({\color{gray}-1.448 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.1 \mathrm{ms} \pm 73.2 \mathrm{μs}\left({\color{gray}-0.204 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$11.2 \mathrm{ms} \pm 55.4 \mathrm{μs}\left({\color{gray}-1.729 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$11.9 \mathrm{ms} \pm 73.2 \mathrm{μs}\left({\color{red}5.53 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.9 \mathrm{ms} \pm 70.0 \mathrm{μs}\left({\color{gray}1.81 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.5 \mathrm{ms} \pm 84.1 \mathrm{μs}\left({\color{gray}1.42 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$11.9 \mathrm{ms} \pm 74.1 \mathrm{μs}\left({\color{red}5.80 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$11.3 \mathrm{ms} \pm 51.1 \mathrm{μs}\left({\color{gray}-0.945 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.3 \mathrm{ms} \pm 62.6 \mathrm{μs}\left({\color{gray}-1.405 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.5 \mathrm{ms} \pm 88.8 \mathrm{μs}\left({\color{gray}1.49 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.9 \mathrm{ms} \pm 76.2 \mathrm{μs}\left({\color{red}5.50 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$12.0 \mathrm{ms} \pm 79.6 \mathrm{μs}\left({\color{gray}5.00 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$8.34 \mathrm{ms} \pm 40.9 \mathrm{μs}\left({\color{gray}-1.415 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$54.6 \mathrm{ms} \pm 442 \mathrm{μs}\left({\color{lightgreen}-9.904 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$107 \mathrm{ms} \pm 560 \mathrm{μs}\left({\color{gray}-4.798 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$62.5 \mathrm{ms} \pm 448 \mathrm{μs}\left({\color{lightgreen}-8.048 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$73.2 \mathrm{ms} \pm 511 \mathrm{μs}\left({\color{lightgreen}-5.588 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$80.9 \mathrm{ms} \pm 508 \mathrm{μs}\left({\color{lightgreen}-6.575 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$86.4 \mathrm{ms} \pm 539 \mathrm{μs}\left({\color{gray}-3.483 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$42.7 \mathrm{ms} \pm 243 \mathrm{μs}\left({\color{lightgreen}-6.570 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$70.2 \mathrm{ms} \pm 454 \mathrm{μs}\left({\color{gray}-3.301 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$49.8 \mathrm{ms} \pm 290 \mathrm{μs}\left({\color{lightgreen}-5.781 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$58.6 \mathrm{ms} \pm 391 \mathrm{μs}\left({\color{gray}-3.205 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$59.6 \mathrm{ms} \pm 339 \mathrm{μs}\left({\color{lightgreen}-5.356 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$59.7 \mathrm{ms} \pm 401 \mathrm{μs}\left({\color{gray}-3.503 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$122 \mathrm{ms} \pm 589 \mathrm{μs}\left({\color{gray}0.645 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$134 \mathrm{ms} \pm 549 \mathrm{μs}\left({\color{gray}0.190 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$22.5 \mathrm{ms} \pm 150 \mathrm{μs}\left({\color{red}20.2 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$551 \mathrm{ms} \pm 1.00 \mathrm{ms}\left({\color{gray}-3.669 \mathrm{\%}}\right) $$ Flame Graph

Adds libs/@local/petrinaut-python: a stdlib-only, POSIX-only package
wrapping the CLI's JSON-lines protocol. PetrinautSession serves a model
(from a file or over stdin) with healthz/metadata/run plus a generic
request method; OptimizationSession adds describe/evaluate/objective over
an opaque manifest, and evaluate returns per-seed replicates when the
manifest asks for more than one seed. The code comes from petrinaut-opt's
client: spawn scrubbing, bounded reads, and process-group shutdown are
unchanged.

petrinaut-opt consumes the package as an editable uv path dependency,
mirrored as a workspace:* edge so Turbo pruning and change detection see
it; its codegen inputs, the Docker python-deps/runner stages, and the
deploy path filter follow. The old src/petrinaut_client.py, the
PYTHON_INTEGRATION.md walkthrough, and examples/python_stdio.py are
retired, and MODEL_EXAMPLES.md now uses the bindings. Two end-to-end
pytests drive the real built CLI bundle, one per session type.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 31 changed files in this pull request and generated no new comments.

Suppressed comments (2)

libs/@local/petrinaut-python/src/petrinaut/session.py:328

  • PetrinautClientError means the process/transport is unusable (errors.py:6-7), and request() explicitly promises that this exception closes the session (lines 204-207). This branch deliberately leaves the session healthy, so callers cannot use the documented exception type to decide whether the session can be reused. Treat non-serializable params as caller input—consistent with the constructor’s ValueErrors—rather than as a transport failure.
    libs/@local/petrinaut-python/README.md:79
  • The uppercase constants are defaults, not constructor option names. A caller following this text and passing BOOTSTRAP_TIMEOUT_SECONDS= will get an unexpected-keyword TypeError; name bootstrap_timeout_seconds and request_timeout_seconds as the options and identify the constants as their defaults.
- Bootstrap (spawn to readiness) and each protocol response have deadlines,
  `BOOTSTRAP_TIMEOUT_SECONDS` and `PROTOCOL_READ_TIMEOUT_SECONDS`. Both are
  constructor options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team type/legal Owned by the @legal team

Development

Successfully merging this pull request may close these issues.

2 participants