agentenv-framework-protocol 0.1.403__tar.gz → 0.1.405__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/PKG-INFO +120 -15
  2. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/README.md +119 -14
  3. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/pyproject.toml +1 -1
  4. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/__init__.py +6 -0
  5. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/agent_env_environment.py +206 -7
  6. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/__init__.py +12 -2
  7. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/_declare.py +2 -1
  8. agentenv_framework_protocol-0.1.405/src/agentenv_protocol/computer/app.py +482 -0
  9. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/driver.py +8 -3
  10. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/extensions.py +1 -1
  11. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/keys.py +2 -0
  12. agentenv_framework_protocol-0.1.405/src/agentenv_protocol/computer/run_install_step.py +43 -0
  13. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/skill.py +30 -7
  14. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/probe_fake.py +1 -1
  15. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_agent_env_environment.py +13 -0
  16. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_app_server.py +14 -5
  17. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_dataplane.py +1 -1
  18. agentenv_framework_protocol-0.1.405/tests/test_computer_declare.py +362 -0
  19. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_driver.py +18 -0
  20. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_extensions.py +4 -4
  21. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_serve.py +57 -2
  22. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_access.py +25 -3
  23. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_annotations.py +155 -1
  24. agentenv_framework_protocol-0.1.403/src/agentenv_protocol/computer/app.py +0 -240
  25. agentenv_framework_protocol-0.1.403/tests/test_computer_declare.py +0 -241
  26. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/.gitignore +0 -0
  27. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/LICENSE +0 -0
  28. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/NOTICE +0 -0
  29. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/THIRD_PARTY_NOTICES.md +0 -0
  30. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/__init__.py +0 -0
  31. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/advanced_agent.py +0 -0
  32. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/basic_agent.py +0 -0
  33. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/custom_extensions_agent.py +0 -0
  34. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/multimodal_agent.py +0 -0
  35. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/reference_model.py +0 -0
  36. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/examples/streaming_agent.py +0 -0
  37. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  38. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  39. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  40. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
  41. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
  42. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
  43. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
  44. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  45. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  46. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  47. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  48. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/client.py +0 -0
  49. /agentenv_framework_protocol-0.1.403/src/agentenv_protocol/computer/linux.py → /agentenv_framework_protocol-0.1.405/src/agentenv_protocol/computer/_linux.py +0 -0
  50. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/_load.py +0 -0
  51. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/app_runtime.py +0 -0
  52. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/apps.py +0 -0
  53. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/card.py +0 -0
  54. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/checks.py +0 -0
  55. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
  56. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
  57. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/checks.py +0 -0
  58. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/client.py +0 -0
  59. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
  60. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
  61. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/conformance/report.py +0 -0
  62. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/dataplane.py +0 -0
  63. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/errors.py +0 -0
  64. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/frames.py +0 -0
  65. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/launch.py +0 -0
  66. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/manifest.py +0 -0
  67. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/operations.py +0 -0
  68. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/platform.py +0 -0
  69. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/queue.py +0 -0
  70. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/recording.py +0 -0
  71. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/results.py +0 -0
  72. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/runtime.py +0 -0
  73. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/serve.py +0 -0
  74. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/serve_app.py +0 -0
  75. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/session.py +0 -0
  76. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/settings.py +0 -0
  77. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/spaces.py +0 -0
  78. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/computer/tools.py +0 -0
  79. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/errors.py +0 -0
  80. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/manifest.py +0 -0
  81. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/preflight.py +0 -0
  82. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/tool_access.py +0 -0
  83. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/tool_client.py +0 -0
  84. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/tool_context.py +0 -0
  85. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/transfers.py +0 -0
  86. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/src/agentenv_protocol/types.py +0 -0
  87. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/computer_fake.py +0 -0
  88. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_agent.py +0 -0
  89. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_agent_examples.py +0 -0
  90. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_object_transfers.py +0 -0
  91. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_peer_agents.py +0 -0
  92. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_proxy.py +0 -0
  93. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_staging.py +0 -0
  94. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_a2a_trajectory_log.py +0 -0
  95. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_client.py +0 -0
  96. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_conformance.py +0 -0
  97. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_conformance_checks.py +0 -0
  98. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_conformance_recording.py +0 -0
  99. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_frames.py +0 -0
  100. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_queue.py +0 -0
  101. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_recording.py +0 -0
  102. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_computer_spaces.py +0 -0
  103. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_data_objects.py +0 -0
  104. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_errors.py +0 -0
  105. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_extension_methods.py +0 -0
  106. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_intake.py +0 -0
  107. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_manifest.py +0 -0
  108. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_preflight.py +0 -0
  109. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_client.py +0 -0
  110. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_context.py +0 -0
  111. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_context_fastmcp.py +0 -0
  112. {agentenv_framework_protocol-0.1.403 → agentenv_framework_protocol-0.1.405}/tests/test_tool_context_injection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentenv-framework-protocol
3
- Version: 0.1.403
3
+ Version: 0.1.405
4
4
  Summary: Open data-plane protocol and server SDK for agent environments.
5
5
  Project-URL: Homepage, https://www.agentenvframework.com
6
6
  Project-URL: Documentation, https://www.agentenvframework.com/docs
@@ -296,6 +296,69 @@ at the gateway.
296
296
 
297
297
  Where the binding lives depends on the app. On mcp's own FastMCP it wraps the tool manager's dispatch; on the standalone `fastmcp` package it joins the app's middleware chain as the outermost layer, so a handler's own middleware runs under it too. An app with neither seam mounts unbound, with a warning, unless a tool declares a `ToolContext` parameter or the handler defines `on_tool_call`: then the mount is refused rather than serving an empty caller.
298
298
 
299
+ ### Tools served on another server's behalf
300
+
301
+ An environment can serve tools another server defines, each with that server's own schema:
302
+ `forwarded_tools()` returns `ForwardedTool(tool, call, annotations, output_schema)` entries, where
303
+ `tool` is an `EnvironmentTool` carrying the other server's `inputSchema`, and `call` takes the
304
+ call's arguments as keywords, untouched, and returns that server's `CallToolResult`, which reaches
305
+ the caller as it is. An `output_schema` is listed as the tool's `outputSchema` and isn't checked
306
+ here, so `call` answers every result, its errors too, in a shape that schema admits. They're listed
307
+ on the card beside the `@tool` methods, chosen by `served_tools` and under tool access like them;
308
+ one named like another tool is refused. They need mcp's FastMCP. The cua-driver driver serves
309
+ cua-driver's own tools this way.
310
+
311
+ ### Resources
312
+
313
+ Mark a method that serves MCP resources with `@resource`; an env may mark several, and each read
314
+ calls the method, which returns text or bytes. The URI decides what's served:
315
+
316
+ ```python
317
+ @environment_card(name="orders")
318
+ class Orders(AgentEnvEnvironment):
319
+ @resource("docs://orders/refund-policy", mime_type="text/markdown")
320
+ def refund_policy(self) -> str:
321
+ """When a refund is allowed and who approves it."""
322
+ return POLICY.read_text()
323
+
324
+ @resource("orders://{order_id}")
325
+ async def order(self, order_id: str) -> str:
326
+ return json.dumps(await self.db.order(order_id))
327
+
328
+ @resource("skill://upstream/{file}", listed=lambda env: UPSTREAM["resources"])
329
+ async def upstream_file(self, file: str) -> str:
330
+ return await read_upstream(f"skill://upstream/{file}")
331
+ ```
332
+
333
+ - A fixed URI is one resource. Its name defaults to the method's name and its description to the
334
+ docstring.
335
+ - A template such as `orders://{order_id}` is an MCP resource template; the method takes its parts.
336
+ - A template with `listed` serves the resources `listed` returns, each as MCP lists one (`uri`,
337
+ `name`, `description`, `mimeType`), and only those. Each is read through the method with its
338
+ URI's parts. That's how an env serves another server's resources from a list: the cua-driver
339
+ driver serves cua-driver's skill files this way.
340
+
341
+ A URI served twice, a listed URI that doesn't match its template, and a method that doesn't take
342
+ exactly the template's parts are refused. Resources need mcp's FastMCP.
343
+
344
+ ### Instructions at connect
345
+
346
+ An MCP server may answer `initialize` with `instructions`: how to use it, which a client may put in
347
+ the agent's context, as Claude Code does. Mark the plain method that returns them with
348
+ `@server_instructions`:
349
+
350
+ ```python
351
+ @environment_card(name="orders")
352
+ class Orders(AgentEnvEnvironment):
353
+ @server_instructions
354
+ def how_to(self) -> str:
355
+ return "Look an order up with get_order before refunding it; refunds can't be undone."
356
+ ```
357
+
358
+ They're read once, when the SDK builds the app (`create_app`, `serve`, `create_fastmcp_app`), and
359
+ every session gets the same text. An env has at most one such method. An app you build yourself
360
+ and pass to `mount` keeps its own instructions: set them when you construct it.
361
+
299
362
  ### Getting part of the data
300
363
 
301
364
  A `@get_data` handler that takes a `paths` parameter returns only the data under the paths a caller
@@ -318,9 +381,11 @@ def guide(self):
318
381
  The card then declares `urn:agentenv:skill/v1` (`SKILL_EXTENSION_URI`), whose `get {name}`
319
382
  answers `{name, description, skill_md}`, the shape `skill-config/v1` takes, with the frontmatter
320
383
  written under the name the caller gives; a name that isn't lowercase letters, digits and single
321
- hyphens, up to 64, is refused `invalid_argument`. agent-env's `add_skills` step installs it on
322
- an agent through `env_ids`, named after the env's environment name; for an env behind a gateway,
323
- whose card nests its servers' cards, it installs each server's skill under that server's name.
384
+ hyphens, up to 64, is refused `invalid_argument`. A skill with other files beside its SKILL.md
385
+ returns them as a third value, `{relative path: text}`, and `get` answers them as `files`.
386
+ agent-env's `add_skills` step installs it on an agent through `env_ids`, named after the env's
387
+ environment name, a skill with files as a bundle of them; for an env behind a gateway, whose card
388
+ nests its servers' cards, it installs each server's skill under that server's name.
324
389
 
325
390
  ### Uploading an export through a grant
326
391
 
@@ -482,7 +547,9 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
482
547
  features. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
483
548
  class): a SKILL.md written from the computer as it booted (its platform, session user and home,
484
549
  the live screen size, what its input takes, its OS's shortcuts) and ending with the driver
485
- class's `notes`, markdown for what its card can't say.
550
+ class's `notes`, markdown for what its card can't say. A driver whose software ships a skill of
551
+ its own returns it from `bundled_skill()` (its description, body and other files), and the
552
+ computer skill leads with it, the computer's part following.
486
553
  - **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
487
554
  capture, the driver's own `ready_checks()`, and one per app on the computer, asked of the app's
488
555
  own server (see Computer apps); tools refuse `device_unavailable` until they pass once), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
@@ -604,37 +671,75 @@ server, which needs the `app` extra (below).
604
671
 
605
672
  ```python
606
673
  from pydantic import BaseModel, SecretStr
607
- from agentenv_protocol.computer import ComputerAppEnvironment, Launch, Linux, Service, computer_app
674
+ from agentenv_protocol.computer import (ComputerAppEnvironment, Launch, Packages, Service, computer_app,
675
+ install_step, linux)
676
+
677
+ class LinuxSettings(BaseModel):
678
+ font: str = "DejaVu Sans"
608
679
 
609
680
  @computer_app(supports=[{"os": {"type": "linux", "display": ["x11"]}, "arch": ["amd64", "arm64"]}])
681
+ @linux(packages=Packages(apt=["thunderbird"]), python="requirements.txt",
682
+ services=[Service(name="imap", command="venv/bin/python imap.py")],
683
+ launch=Launch(command=["thunderbird"], window={"class": "thunderbird"}), settings=LinuxSettings)
610
684
  class MailApp(ComputerAppEnvironment):
611
- linux = Linux(python="requirements.txt", services=[Service(name="imap", command="venv/bin/python imap.py")])
612
- launch = Launch(command=["thunderbird"], window={"class": "thunderbird"})
613
685
  skill = "skill.md"
614
686
 
615
687
  class Settings(BaseModel):
616
688
  signature: str = ""
617
689
  password: SecretStr = SecretStr("")
690
+
691
+ class BuildSettings(BaseModel):
692
+ locale: str = "en_US"
693
+
694
+ @install_step(scope="session")
695
+ def profile(self) -> None: ...
618
696
  ```
619
697
 
620
698
  - **`@computer_app`** names the platforms the app supports, as a driver names them, and the
621
699
  `permissions` it needs of the computer. Its env.toml names the rest: the app itself
622
- (`environment_name`) and the `computer` and `driver` it deploys alone on.
623
- - **`linux = Linux(...)`** is how a Linux computer installs it: `files`, a folder of the app's
624
- copied under its prefix, which holds the class's file; `python`, a requirements file there,
625
- installed into the venv every app gets beside them; `services`, programs the computer's init runs
626
- from the prefix as the session user.
627
- - **`launch = Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
700
+ (`environment_name`), the `computer` and `driver` it deploys alone on, and `build_settings`, its
701
+ build settings' values (`build_settings = { locale = "fr_FR" }`).
702
+ - **`@linux(...)`** is how a Linux computer installs it, `Linux`'s fields: `files`, a folder of the app's
703
+ copied under its prefix, which holds the class's file; `packages`, the OS's, as apt takes them
704
+ (`Packages(apt=["name", "name=version", "name/suite"])`); `python`, a requirements file there,
705
+ installed into the venv every app gets beside them; `sources`, extra apt repositories
706
+ (`AptSource`, with a key file in the folder); `downloads`, files fetched by URL and checked by
707
+ sha256 (`Download`); `run`, scripts of the folder run as root or the session user (`RunScript`);
708
+ `services`, programs the computer's init runs from the prefix as the session user; and `ports`,
709
+ those it listens on. Packages, requirements and downloads are resolved and fetched once, when the
710
+ computer env is put, and every build installs only those stored files, with the network closed.
711
+ Each value is checked as the class is defined: a URL, a sha256, a path that stays in the folder.
712
+ Installs are exact: apt installs each package without its recommends, and the venv gets exactly
713
+ the wheels resolved for it (`pip install --no-deps`). A requirements file lists everything itself,
714
+ since its lock covers that file alone: a line including another file (`-r`, `-c`), an editable
715
+ install (`-e`), a local path or a local folder of wheels is refused at put.
716
+ - **Each OS has its own decorator,** and what differs by OS goes in it: `launch=` always; and
717
+ optionally `skill=`, a file used instead of the class's, and `settings=` and `build_settings=`,
718
+ models whose fields add to the class's `Settings` and `BuildSettings` on that OS. What's on the
719
+ class applies to every OS. `@install(os_type, spec, launch=..., ...)` is the generic form, for an
720
+ OS type a plugin adds, with that OS type's own install model as `spec`; `@linux(...)` is
721
+ `@install("linux", Linux(...), ...)`. On the computer the class reads what's declared for the OS
722
+ it runs on (`platform.detect()`).
723
+ - **`launch=Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
628
724
  the agent opens it (`"demand"`). `ready=Ready(...)` says when it's ready, within `timeout_s` of its
629
725
  launch: a window matching `launch.window` by its class, instance or title, a port answering on
630
726
  loopback, or a command exiting 0. `@ready_check` marks a method for readiness that needs logic (a
631
727
  sync one runs in a thread), and `@install_step(scope=...)` one a computer runs as it installs the
632
- app.
728
+ app, after its run scripts, offline and without the class's `__init__`. Each runs on every OS the
729
+ app installs on, or on those its `os=` names (`@install_step(os="linux")`, `@ready_check(os=[...])`);
730
+ naming one the app declares no install for is refused when the class is read. A step reads
731
+ `self.build_settings`; a deploy's `self.settings` aren't known at build, and reading one raises
732
+ `AttributeError` (`python -m agentenv_protocol.computer.run_install_step <file>:<Class> <method>`).
633
733
  - **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both;
634
734
  its data hooks are the usual `@add_data`, `@get_data` and `@reset_data`.
635
735
  - **`Settings`** is typed; a computer hands each app the values a deploy gives it, as one JSON value
636
736
  in `APP_SETTINGS_ENV` (`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads
637
737
  its own, by `ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
738
+ - **`BuildSettings`** is typed too: values the app's env fixes in its computer's image, which only
739
+ another image changes, so a deploy can't give one. The composer writes them to
740
+ `.agentenv/build_settings.json` in the app's folder, and the base class reads them, over the
741
+ defaults, as `self.build_settings`, in its install steps and on the computer alike. None is a
742
+ secret, since the image keeps them; `declaration()` refuses a `SecretStr` one.
638
743
 
639
744
  On a computer each app is its own server: `python -m agentenv_protocol.computer.serve_app
640
745
  <file>:<Class>`, a program the computer's init runs from the app's folder in the app's venv, which
@@ -240,6 +240,69 @@ at the gateway.
240
240
 
241
241
  Where the binding lives depends on the app. On mcp's own FastMCP it wraps the tool manager's dispatch; on the standalone `fastmcp` package it joins the app's middleware chain as the outermost layer, so a handler's own middleware runs under it too. An app with neither seam mounts unbound, with a warning, unless a tool declares a `ToolContext` parameter or the handler defines `on_tool_call`: then the mount is refused rather than serving an empty caller.
242
242
 
243
+ ### Tools served on another server's behalf
244
+
245
+ An environment can serve tools another server defines, each with that server's own schema:
246
+ `forwarded_tools()` returns `ForwardedTool(tool, call, annotations, output_schema)` entries, where
247
+ `tool` is an `EnvironmentTool` carrying the other server's `inputSchema`, and `call` takes the
248
+ call's arguments as keywords, untouched, and returns that server's `CallToolResult`, which reaches
249
+ the caller as it is. An `output_schema` is listed as the tool's `outputSchema` and isn't checked
250
+ here, so `call` answers every result, its errors too, in a shape that schema admits. They're listed
251
+ on the card beside the `@tool` methods, chosen by `served_tools` and under tool access like them;
252
+ one named like another tool is refused. They need mcp's FastMCP. The cua-driver driver serves
253
+ cua-driver's own tools this way.
254
+
255
+ ### Resources
256
+
257
+ Mark a method that serves MCP resources with `@resource`; an env may mark several, and each read
258
+ calls the method, which returns text or bytes. The URI decides what's served:
259
+
260
+ ```python
261
+ @environment_card(name="orders")
262
+ class Orders(AgentEnvEnvironment):
263
+ @resource("docs://orders/refund-policy", mime_type="text/markdown")
264
+ def refund_policy(self) -> str:
265
+ """When a refund is allowed and who approves it."""
266
+ return POLICY.read_text()
267
+
268
+ @resource("orders://{order_id}")
269
+ async def order(self, order_id: str) -> str:
270
+ return json.dumps(await self.db.order(order_id))
271
+
272
+ @resource("skill://upstream/{file}", listed=lambda env: UPSTREAM["resources"])
273
+ async def upstream_file(self, file: str) -> str:
274
+ return await read_upstream(f"skill://upstream/{file}")
275
+ ```
276
+
277
+ - A fixed URI is one resource. Its name defaults to the method's name and its description to the
278
+ docstring.
279
+ - A template such as `orders://{order_id}` is an MCP resource template; the method takes its parts.
280
+ - A template with `listed` serves the resources `listed` returns, each as MCP lists one (`uri`,
281
+ `name`, `description`, `mimeType`), and only those. Each is read through the method with its
282
+ URI's parts. That's how an env serves another server's resources from a list: the cua-driver
283
+ driver serves cua-driver's skill files this way.
284
+
285
+ A URI served twice, a listed URI that doesn't match its template, and a method that doesn't take
286
+ exactly the template's parts are refused. Resources need mcp's FastMCP.
287
+
288
+ ### Instructions at connect
289
+
290
+ An MCP server may answer `initialize` with `instructions`: how to use it, which a client may put in
291
+ the agent's context, as Claude Code does. Mark the plain method that returns them with
292
+ `@server_instructions`:
293
+
294
+ ```python
295
+ @environment_card(name="orders")
296
+ class Orders(AgentEnvEnvironment):
297
+ @server_instructions
298
+ def how_to(self) -> str:
299
+ return "Look an order up with get_order before refunding it; refunds can't be undone."
300
+ ```
301
+
302
+ They're read once, when the SDK builds the app (`create_app`, `serve`, `create_fastmcp_app`), and
303
+ every session gets the same text. An env has at most one such method. An app you build yourself
304
+ and pass to `mount` keeps its own instructions: set them when you construct it.
305
+
243
306
  ### Getting part of the data
244
307
 
245
308
  A `@get_data` handler that takes a `paths` parameter returns only the data under the paths a caller
@@ -262,9 +325,11 @@ def guide(self):
262
325
  The card then declares `urn:agentenv:skill/v1` (`SKILL_EXTENSION_URI`), whose `get {name}`
263
326
  answers `{name, description, skill_md}`, the shape `skill-config/v1` takes, with the frontmatter
264
327
  written under the name the caller gives; a name that isn't lowercase letters, digits and single
265
- hyphens, up to 64, is refused `invalid_argument`. agent-env's `add_skills` step installs it on
266
- an agent through `env_ids`, named after the env's environment name; for an env behind a gateway,
267
- whose card nests its servers' cards, it installs each server's skill under that server's name.
328
+ hyphens, up to 64, is refused `invalid_argument`. A skill with other files beside its SKILL.md
329
+ returns them as a third value, `{relative path: text}`, and `get` answers them as `files`.
330
+ agent-env's `add_skills` step installs it on an agent through `env_ids`, named after the env's
331
+ environment name, a skill with files as a bundle of them; for an env behind a gateway, whose card
332
+ nests its servers' cards, it installs each server's skill under that server's name.
268
333
 
269
334
  ### Uploading an export through a grant
270
335
 
@@ -426,7 +491,9 @@ driver has them, through the `served_tools`, `served_extensions` and `served_ope
426
491
  features. The driver serves its skill through `skill/v1` (an `@agent_skill` handler on the base
427
492
  class): a SKILL.md written from the computer as it booted (its platform, session user and home,
428
493
  the live screen size, what its input takes, its OS's shortcuts) and ending with the driver
429
- class's `notes`, markdown for what its card can't say.
494
+ class's `notes`, markdown for what its card can't say. A driver whose software ships a skill of
495
+ its own returns it from `bundled_skill()` (its description, body and other files), and the
496
+ computer skill leads with it, the computer's part following.
430
497
  - **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
431
498
  capture, the driver's own `ready_checks()`, and one per app on the computer, asked of the app's
432
499
  own server (see Computer apps); tools refuse `device_unavailable` until they pass once), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
@@ -548,37 +615,75 @@ server, which needs the `app` extra (below).
548
615
 
549
616
  ```python
550
617
  from pydantic import BaseModel, SecretStr
551
- from agentenv_protocol.computer import ComputerAppEnvironment, Launch, Linux, Service, computer_app
618
+ from agentenv_protocol.computer import (ComputerAppEnvironment, Launch, Packages, Service, computer_app,
619
+ install_step, linux)
620
+
621
+ class LinuxSettings(BaseModel):
622
+ font: str = "DejaVu Sans"
552
623
 
553
624
  @computer_app(supports=[{"os": {"type": "linux", "display": ["x11"]}, "arch": ["amd64", "arm64"]}])
625
+ @linux(packages=Packages(apt=["thunderbird"]), python="requirements.txt",
626
+ services=[Service(name="imap", command="venv/bin/python imap.py")],
627
+ launch=Launch(command=["thunderbird"], window={"class": "thunderbird"}), settings=LinuxSettings)
554
628
  class MailApp(ComputerAppEnvironment):
555
- linux = Linux(python="requirements.txt", services=[Service(name="imap", command="venv/bin/python imap.py")])
556
- launch = Launch(command=["thunderbird"], window={"class": "thunderbird"})
557
629
  skill = "skill.md"
558
630
 
559
631
  class Settings(BaseModel):
560
632
  signature: str = ""
561
633
  password: SecretStr = SecretStr("")
634
+
635
+ class BuildSettings(BaseModel):
636
+ locale: str = "en_US"
637
+
638
+ @install_step(scope="session")
639
+ def profile(self) -> None: ...
562
640
  ```
563
641
 
564
642
  - **`@computer_app`** names the platforms the app supports, as a driver names them, and the
565
643
  `permissions` it needs of the computer. Its env.toml names the rest: the app itself
566
- (`environment_name`) and the `computer` and `driver` it deploys alone on.
567
- - **`linux = Linux(...)`** is how a Linux computer installs it: `files`, a folder of the app's
568
- copied under its prefix, which holds the class's file; `python`, a requirements file there,
569
- installed into the venv every app gets beside them; `services`, programs the computer's init runs
570
- from the prefix as the session user.
571
- - **`launch = Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
644
+ (`environment_name`), the `computer` and `driver` it deploys alone on, and `build_settings`, its
645
+ build settings' values (`build_settings = { locale = "fr_FR" }`).
646
+ - **`@linux(...)`** is how a Linux computer installs it, `Linux`'s fields: `files`, a folder of the app's
647
+ copied under its prefix, which holds the class's file; `packages`, the OS's, as apt takes them
648
+ (`Packages(apt=["name", "name=version", "name/suite"])`); `python`, a requirements file there,
649
+ installed into the venv every app gets beside them; `sources`, extra apt repositories
650
+ (`AptSource`, with a key file in the folder); `downloads`, files fetched by URL and checked by
651
+ sha256 (`Download`); `run`, scripts of the folder run as root or the session user (`RunScript`);
652
+ `services`, programs the computer's init runs from the prefix as the session user; and `ports`,
653
+ those it listens on. Packages, requirements and downloads are resolved and fetched once, when the
654
+ computer env is put, and every build installs only those stored files, with the network closed.
655
+ Each value is checked as the class is defined: a URL, a sha256, a path that stays in the folder.
656
+ Installs are exact: apt installs each package without its recommends, and the venv gets exactly
657
+ the wheels resolved for it (`pip install --no-deps`). A requirements file lists everything itself,
658
+ since its lock covers that file alone: a line including another file (`-r`, `-c`), an editable
659
+ install (`-e`), a local path or a local folder of wheels is refused at put.
660
+ - **Each OS has its own decorator,** and what differs by OS goes in it: `launch=` always; and
661
+ optionally `skill=`, a file used instead of the class's, and `settings=` and `build_settings=`,
662
+ models whose fields add to the class's `Settings` and `BuildSettings` on that OS. What's on the
663
+ class applies to every OS. `@install(os_type, spec, launch=..., ...)` is the generic form, for an
664
+ OS type a plugin adds, with that OS type's own install model as `spec`; `@linux(...)` is
665
+ `@install("linux", Linux(...), ...)`. On the computer the class reads what's declared for the OS
666
+ it runs on (`platform.detect()`).
667
+ - **`launch=Launch(...)`** says how it starts in the desktop session: with it (`at="boot"`) or when
572
668
  the agent opens it (`"demand"`). `ready=Ready(...)` says when it's ready, within `timeout_s` of its
573
669
  launch: a window matching `launch.window` by its class, instance or title, a port answering on
574
670
  loopback, or a command exiting 0. `@ready_check` marks a method for readiness that needs logic (a
575
671
  sync one runs in a thread), and `@install_step(scope=...)` one a computer runs as it installs the
576
- app.
672
+ app, after its run scripts, offline and without the class's `__init__`. Each runs on every OS the
673
+ app installs on, or on those its `os=` names (`@install_step(os="linux")`, `@ready_check(os=[...])`);
674
+ naming one the app declares no install for is refused when the class is read. A step reads
675
+ `self.build_settings`; a deploy's `self.settings` aren't known at build, and reading one raises
676
+ `AttributeError` (`python -m agentenv_protocol.computer.run_install_step <file>:<Class> <method>`).
577
677
  - **Its skill** is a file of its folder (`skill = "skill.md"`) or an `@agent_skill` method, not both;
578
678
  its data hooks are the usual `@add_data`, `@get_data` and `@reset_data`.
579
679
  - **`Settings`** is typed; a computer hands each app the values a deploy gives it, as one JSON value
580
680
  in `APP_SETTINGS_ENV` (`AGENTENV_APP_SETTINGS`, the apps' values by name), and the base class reads
581
681
  its own, by `ENVIRONMENT_NAME`, as `self.settings`. A `SecretStr` field is a secret.
682
+ - **`BuildSettings`** is typed too: values the app's env fixes in its computer's image, which only
683
+ another image changes, so a deploy can't give one. The composer writes them to
684
+ `.agentenv/build_settings.json` in the app's folder, and the base class reads them, over the
685
+ defaults, as `self.build_settings`, in its install steps and on the computer alike. None is a
686
+ secret, since the image keeps them; `declaration()` refuses a `SecretStr` one.
582
687
 
583
688
  On a computer each app is its own server: `python -m agentenv_protocol.computer.serve_app
584
689
  <file>:<Class>`, a program the computer's init runs from the app's folder in the app's venv, which
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.403"
3
+ version = "0.1.405"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -11,8 +11,11 @@ from .agent_env_environment import (
11
11
  AgentEnvEnvironment,
12
12
  AgentEnvFastMCPApplication,
13
13
  AgentEnvStarletteApplication,
14
+ ForwardedTool,
14
15
  add_data,
15
16
  agent_skill,
17
+ resource,
18
+ server_instructions,
16
19
  create_fastmcp_app,
17
20
  environment_card,
18
21
  extension,
@@ -88,12 +91,15 @@ __all__ = [
88
91
  "AgentEnvEnvironment",
89
92
  "AgentEnvFastMCPApplication",
90
93
  "AgentEnvStarletteApplication",
94
+ "ForwardedTool",
91
95
  "create_fastmcp_app",
92
96
  "environment_card",
93
97
  "reset_data",
94
98
  "add_data",
95
99
  "get_data",
96
100
  "agent_skill",
101
+ "resource",
102
+ "server_instructions",
97
103
  "extension",
98
104
  "tool",
99
105
  "injecting",