vis-agent 0.1.43__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 (54) hide show
  1. vis_agent-0.1.43/.gitignore +106 -0
  2. vis_agent-0.1.43/LICENSE +190 -0
  3. vis_agent-0.1.43/PKG-INFO +380 -0
  4. vis_agent-0.1.43/README.md +360 -0
  5. vis_agent-0.1.43/contracts/activity.json +50 -0
  6. vis_agent-0.1.43/contracts/config.json +64 -0
  7. vis_agent-0.1.43/contracts/content.json +50 -0
  8. vis_agent-0.1.43/contracts/fixtures/activity-cases.json +2008 -0
  9. vis_agent-0.1.43/contracts/fixtures/activity.json +53 -0
  10. vis_agent-0.1.43/contracts/fixtures/view.json +79 -0
  11. vis_agent-0.1.43/contracts/gateway.json +1331 -0
  12. vis_agent-0.1.43/contracts/provider.json +51 -0
  13. vis_agent-0.1.43/contracts/python-host.json +137 -0
  14. vis_agent-0.1.43/contracts/schema/activity.json +935 -0
  15. vis_agent-0.1.43/contracts/schema/common.json +43 -0
  16. vis_agent-0.1.43/contracts/schema/config.json +1264 -0
  17. vis_agent-0.1.43/contracts/schema/content.json +444 -0
  18. vis_agent-0.1.43/contracts/schema/gateway.json +509 -0
  19. vis_agent-0.1.43/contracts/schema/provider.json +254 -0
  20. vis_agent-0.1.43/contracts/schema/python-host.json +126 -0
  21. vis_agent-0.1.43/contracts/schema/surface.json +484 -0
  22. vis_agent-0.1.43/contracts/schema/test-runner.json +193 -0
  23. vis_agent-0.1.43/contracts/schema/toggle.json +126 -0
  24. vis_agent-0.1.43/contracts/schema/view.json +2513 -0
  25. vis_agent-0.1.43/contracts/surface.json +8 -0
  26. vis_agent-0.1.43/contracts/test-runner.json +23 -0
  27. vis_agent-0.1.43/contracts/toggle.json +30 -0
  28. vis_agent-0.1.43/contracts/view.json +119 -0
  29. vis_agent-0.1.43/hatch_build.py +19 -0
  30. vis_agent-0.1.43/pyproject.toml +40 -0
  31. vis_agent-0.1.43/src/blockether/vis/__init__.py +6 -0
  32. vis_agent-0.1.43/src/blockether/vis/_contracts.py +142 -0
  33. vis_agent-0.1.43/src/blockether/vis/_outside.py +1099 -0
  34. vis_agent-0.1.43/src/blockether/vis/_wire.py +28 -0
  35. vis_agent-0.1.43/src/blockether/vis/activity.py +185 -0
  36. vis_agent-0.1.43/src/blockether/vis/engine/__init__.py +41 -0
  37. vis_agent-0.1.43/src/blockether/vis/engine/_client.py +2420 -0
  38. vis_agent-0.1.43/src/blockether/vis/engine/_local.py +258 -0
  39. vis_agent-0.1.43/src/blockether/vis/extension.py +3018 -0
  40. vis_agent-0.1.43/src/blockether/vis/py.typed +0 -0
  41. vis_agent-0.1.43/src/blockether/vis/views.py +154 -0
  42. vis_agent-0.1.43/tests/conftest.py +26 -0
  43. vis_agent-0.1.43/tests/test_activity.py +171 -0
  44. vis_agent-0.1.43/tests/test_client.py +577 -0
  45. vis_agent-0.1.43/tests/test_contracts.py +289 -0
  46. vis_agent-0.1.43/tests/test_declarations.py +251 -0
  47. vis_agent-0.1.43/tests/test_engine.py +389 -0
  48. vis_agent-0.1.43/tests/test_local.py +241 -0
  49. vis_agent-0.1.43/tests/test_namespace.py +111 -0
  50. vis_agent-0.1.43/tests/test_outside.py +632 -0
  51. vis_agent-0.1.43/tests/test_providers.py +323 -0
  52. vis_agent-0.1.43/tests/test_registration.py +132 -0
  53. vis_agent-0.1.43/tests/test_testing.py +136 -0
  54. vis_agent-0.1.43/tests/test_views.py +164 -0
@@ -0,0 +1,106 @@
1
+ # Build / cache - all globs are leading-slash-free on purpose so they
2
+ # match at any depth (root, packages/*, extensions/*, docs/, etc.).
3
+ .cpcache/
4
+ .cache/
5
+ # clj-kondo: the local cache is generated, but config.edn, hooks/ AND the
6
+ # configs imported from dependencies (`imports/`) are the lint rules — without
7
+ # them a fresh checkout resolves no lazytest/malli macro and `-M:lint`
8
+ # fails only on CI. Refresh them with:
9
+ # clojure -M:test:lint -m clj-kondo.main --copy-configs --dependencies \
10
+ # --lint "$(clojure -Spath -M:test)"
11
+ .clj-kondo/.cache/
12
+ .clj-kondo/inline-configs/
13
+ .lsp/
14
+ .shadow-cljs/
15
+ target/
16
+ .pi/extensions/*/node_modules/
17
+ node_modules/
18
+ .expo/
19
+
20
+
21
+ # IDE
22
+ .idea/
23
+ *.iml
24
+ .vscode/
25
+ .calva/
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # Project-specific docs (not tracked)
32
+ GOALS.md
33
+ HERMES_ARCHITECTURE.md
34
+ LANTERNA_3X_API_REFERENCE.md
35
+
36
+ # Test data (large / binary)
37
+ schema-therapy.pdf
38
+ schema-therapy.pageindex/
39
+ # Screen recordings / large media scratch — too big for GitHub (>100 MB),
40
+ # and `git add -A` should never stage them. Use Git LFS if any must be tracked.
41
+ *.mp4
42
+ *.mov
43
+ siema.mp4
44
+
45
+ # Bench trajectories / results / harness output (large, generated).
46
+ # Datasets under packages/vis-benchmark/data/ ARE tracked.
47
+ packages/vis-benchmark/results/
48
+ packages/vis-benchmark/trajectories/
49
+ packages/vis-benchmark/swebench-harness/
50
+
51
+ # Sessions / runtime data
52
+ .omc/
53
+ .zcode/
54
+ .nrepl-port
55
+ .nrepl-session
56
+ .nrepl.log
57
+ .nrepl.boot.log
58
+ .nrepl.pid
59
+ # The root docs/ folder is LOCAL SCRATCH only (e.g. mdbook-mermaid assets,
60
+ # regenerable via `mdbook-mermaid install docs`). Canonical documentation is
61
+ # tracked under resources/vis-docs/ — never the root docs/ tree. Leading slash
62
+ # scopes this to the repo root, so extension `*/docs/` dirs stay tracked.
63
+ /docs/
64
+
65
+ jobs/
66
+ # Legacy local verification logs may still exist after removing verify.sh.
67
+ .verification/
68
+ # rift/bridge per-directory ULID marker — regenerated at runtime, never source.
69
+ .rift
70
+ # Claude Code local tooling state (machine-local settings + run locks).
71
+ .claude/
72
+ hs_err_pid*.log
73
+
74
+ # python bytecode
75
+ __pycache__/
76
+ *.pyc
77
+
78
+ # transient dev logs
79
+ .docs-dev.log
80
+
81
+ # transient slack dump / agent scratch (never commit)
82
+ igor-dump/
83
+ channels-*.csv
84
+ .todos/
85
+ .sidecar/
86
+ .sidecar-agent
87
+ .sidecar-task
88
+ .sidecar-pr
89
+ .sidecar-start.sh
90
+ .sidecar-base
91
+ .td-root
92
+
93
+ # local git worktrees (CI native-build probes, etc.)
94
+ .gitworktrees/
95
+
96
+ # local talk deck — source pptx + generated HTML presentation (never committed)
97
+ own_your_harness.pptx
98
+ /presentation/
99
+
100
+ # Cloudflare Wrangler local dev state (KV/D1/DO simulators, cached account info).
101
+ # Glob is leading-slash-free so it also covers apps/vis-companion-relay/.wrangler/;
102
+ # the tracked wrangler.jsonc config is unaffected.
103
+ .wrangler/
104
+
105
+ # Voice assets built by bin/export-pocket-tts — release payloads, never sources.
106
+ /dist/
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2025-2026 Blockether
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
@@ -0,0 +1,380 @@
1
+ Metadata-Version: 2.5
2
+ Name: vis-agent
3
+ Version: 0.1.43
4
+ Summary: The Vis Python SDK — author extensions and control local or remote engines.
5
+ Project-URL: Homepage, https://github.com/Blockether/vis
6
+ Project-URL: Source, https://github.com/Blockether/vis
7
+ Project-URL: Issues, https://github.com/Blockether/vis/issues
8
+ Author: Blockether
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: agent,automation,extension,human-in-the-loop,vis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: jsonschema<5,>=4.23
19
+ Description-Content-Type: text/markdown
20
+
21
+ # vis-agent
22
+
23
+ The Python SDK for [Vis](https://github.com/Blockether/vis): author extensions
24
+ and control local or remote engines.
25
+
26
+ One distribution, `vis-agent`, exposes two entry points: `blockether.vis.extension`
27
+ for extension authors and `blockether.vis.engine` for engine clients. Models live
28
+ in `blockether.vis.views` and `blockether.vis.activity`. The lightweight
29
+ `blockether.vis` root initializes no host and starts no process or connection.
30
+ `blockether` is an implicit PEP 420 namespace; the SDK does not own its `__init__.py`.
31
+ The SDK does not install or alias the unrelated top-level `vis` package.
32
+
33
+ SDK records, including `Response`, `Event`, `Session`, `Turn`, View and Activity
34
+ projections, are frozen, slotted dataclasses. Event envelope fields (`type`,
35
+ `session_id`, `seq`, `cursor`, `turn_id`) are named and validated on both transports.
36
+ `Event.view` and `Event.activity` expose validated records with immutable nested
37
+ collections. `Event.data` holds the remaining dynamic payload and remains mutable;
38
+ frozen records do not promise deep immutability for arbitrary endpoint data.
39
+ Extension tools can return frozen/slotted dataclasses; the runtime serializes
40
+ their declared fields, including nested records, without requiring `__dict__`.
41
+ The SDK ships PEP 561 `py.typed` metadata for its inline annotations.
42
+ The remaining endpoint dictionaries are not yet a fully typed model API.
43
+
44
+ ```bash
45
+ pip install vis-agent
46
+ ```
47
+
48
+ ```python
49
+ from dataclasses import dataclass
50
+
51
+ import blockether.vis.extension as vis
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class Greeting:
56
+ text: str
57
+
58
+
59
+ def greet(name: str) -> Greeting:
60
+ """Greet one person and return a typed result."""
61
+ vis.publish_activity(
62
+ vis.ActivityPresentation(
63
+ "Greeting", "Preparing reply", (vis.ActivityProgress("Working"),)
64
+ )
65
+ )
66
+ return Greeting(f"Hello, {name}!")
67
+
68
+
69
+ def greeting_activity(phase, result, **_) -> vis.ActivityPresentation:
70
+ return vis.ActivityPresentation(
71
+ "Greeting",
72
+ phase,
73
+ (vis.ActivityText(result.text if phase == "success" else "Preparing reply"),),
74
+ )
75
+
76
+
77
+ vis.register(
78
+ vis.Extension(
79
+ name="greeter",
80
+ description="Greeting tools.",
81
+ alias="greeter",
82
+ symbols=[vis.Symbol(greet, activity=vis.Activity(render=greeting_activity))],
83
+ )
84
+ )
85
+ ```
86
+
87
+ `Extension`, `Symbol`, `SlashCommand`, `OpHook`, `NetworkFilter` and `Provider`
88
+ are frozen, slotted declarations. Constructing them performs no host IO. Only
89
+ `vis.register(extension)` registers the file and resolves its declared environment;
90
+ call it once. Collections are snapshotted, and marker dictionaries are not accepted.
91
+ `vis` is the imported module, not a singleton or an extension base class.
92
+
93
+ | API | Purpose |
94
+ | --- | --- |
95
+ | `log(level, message)` | Diagnostics, not model context. |
96
+ | `notify(text)` | One-way notification to the human. |
97
+ | `ask(title, fields)` | Wait for a typed human-input result. |
98
+ | `Activity` / `publish_activity(presentation)` | This tool invocation's progress and presentation; the engine owns timing and outcome. |
99
+ ## Providers
100
+
101
+ Provider presets and callback results use typed records too:
102
+
103
+ ```python
104
+ import os
105
+ import blockether.vis.extension as vis
106
+
107
+
108
+ def credential() -> vis.ProviderCredential | None:
109
+ token = os.environ.get("EXAMPLE_API_KEY")
110
+ return vis.ProviderCredential(token) if token else None
111
+
112
+
113
+ def status() -> vis.ProviderStatus:
114
+ return vis.ProviderStatus(
115
+ is_authenticated=bool(os.environ.get("EXAMPLE_API_KEY")),
116
+ source="env-var",
117
+ )
118
+
119
+
120
+ vis.register(
121
+ vis.Extension(
122
+ name="provider-example",
123
+ description="An OpenAI-compatible provider.",
124
+ env=["EXAMPLE_API_KEY"],
125
+ providers=[
126
+ vis.Provider(
127
+ id="example",
128
+ label="Example AI",
129
+ preset=vis.ProviderPreset(
130
+ base_url="https://gateway.example.com/v1",
131
+ api_style="openai",
132
+ default_models=["example-model"],
133
+ ),
134
+ get_token_fn=credential,
135
+ status_fn=status,
136
+ )
137
+ ],
138
+ )
139
+ )
140
+ ```
141
+
142
+ Load this file as an extension, then add/select `example` in Vis. Credentials are
143
+ read passively; return `None` when absent. Set `is_managed=True` only when the
144
+ extension owns automatic binding/configuration; it does not disable authentication.
145
+ Refresh, login/logout, limits and model enrichment use the same provider boundary.
146
+ See [provider callbacks and limits](../../resources/vis-docs/extending.md#llm-providers)
147
+ for their signatures and typed results. No constructor invokes a callback.
148
+
149
+ ## One file, two hosts
150
+
151
+ Host operations are declared in
152
+ [`python-host.json`](https://github.com/Blockether/vis/blob/main/packages/vis-contract/resources/vis-contract/python-host.json)
153
+ and bundled with the other canonical documents in the SDK. Contract reading and
154
+ validation are private implementation details, not a separate Python package.
155
+
156
+ Inside a Vis session the engine seeds those ops and they reach the live agent:
157
+ state is the extension's durable state, `vis.ask` opens a dialog on whichever
158
+ surface the human is using, `vis.shell` runs in the agent's sandbox.
159
+
160
+ Installed in an ordinary Python environment there is no agent, so
161
+ `blockether.vis._outside` supplies the host operations:
162
+
163
+ | op | outside |
164
+ | --- | --- |
165
+ | `state`, `log`, `notify`, `shell`, secrets, `host_env` | done locally — a JSON file under `~/.vis/outside`, stderr lines, a real subprocess, a process-local vault |
166
+ | `ask` | prompted in the TERMINAL: the same field tree, the same validators, the same `Answer` |
167
+ | `jailed_shell`, `jailed_shell_session` | refused by name — a jail is a property of the agent's process boundary, and nothing out here can enforce one |
168
+
169
+ So an extension file imports, type-checks, unit-tests and runs on a laptop or in
170
+ CI, and the code that ships is the code that was tested.
171
+
172
+ ## Answering without a human
173
+
174
+ ```python
175
+ import blockether.vis.extension as vis
176
+
177
+ vis.outside.answer_with({"env": "staging", "token": "hunter2"})
178
+ answer = vis.ask("Deploy", [vis.select("env", ["staging", "prod"])])
179
+ assert answer["env"] == "staging"
180
+ ```
181
+
182
+ `VIS_OUTSIDE_ANSWERS` (a JSON object) primes the same values from the
183
+ environment, and `VIS_OUTSIDE_NONINTERACTIVE=1` makes an unanswerable ask return
184
+ `undeliverable` — exactly what a session with no surface mounted returns — instead
185
+ of blocking a build.
186
+
187
+ Other environment knobs: `VIS_OUTSIDE_HOME` moves the state file and the shell
188
+ logs (default `~/.vis/outside`).
189
+
190
+ ## Testing live extensions
191
+
192
+ `vis.testing.LiveRecorder` is the shared in-memory host for extension tests. It
193
+ records extension envelopes without publishing fixture views into a real session,
194
+ materializes open/patch/state/close, and exposes `focus` and `close` for simulated
195
+ surface actions. Provider-specific tests keep only their provider snapshots and
196
+ assertions; `vis.testing.assert_tree` compares terminal view goldens at the exact
197
+ leaf that changed.
198
+
199
+ ## View and Activity are different contracts
200
+
201
+ **View** is a human interaction: an input form or a live surface with the existing
202
+ `view.open`, `view.patch` and `view.close` lifecycle. Extension authors keep using
203
+ `vis.ask` and the live builders. Remote and local clients use the same typed
204
+ `InputView`, `LiveView`, `LivePatch`, `InputResult` and `LiveResult` records from
205
+ `blockether.vis.views`. `session.input_views()` and `session.live_views()` return
206
+ records, not unvalidated dictionaries. `session.answer(view_id, values)` submits a
207
+ form; `session.view_action(view_id, action, **values)` validates the closed action
208
+ shape before transport IO. Malformed incoming views raise `ProtocolError`.
209
+ Input projections carry the engine's `created_at`, not channel routing or validator
210
+ callbacks. An `InputResult` contains only the close reason: submitted values and
211
+ secret handles go to the waiting extension, not the event stream. SSE omits the
212
+ already streamed live picture, so `LiveResult.view` is optional; retain the open
213
+ View and apply its patches if your application needs a materialized display.
214
+
215
+ **Activity** is engine-observed execution evidence, not an interactive View and not
216
+ model context. An extension can set `vis.Activity(presenter=..., label=...)` on a
217
+ symbol or `vis.method`; it cannot forge lifecycle identity, timing or outcomes.
218
+ The tool's observation/mutation tag still determines its effect classification.
219
+ Omitting Activity metadata preserves the engine's default presentation.
220
+
221
+ ```python
222
+ # The same records are returned by GatewayClient and LocalEngine.
223
+ with session.events() as events:
224
+ for event in events:
225
+ if event.activity is not None:
226
+ print(event.activity.state, event.activity.counts)
227
+ if event.view is not None:
228
+ print(event.type, event.view.kind, event.view.view_id)
229
+ ```
230
+
231
+ `blockether.vis.activity.ActivityProjection` represents the complete replacement
232
+ in a `block.activity` event; it is not a delta to merge. Rows, counts, omission
233
+ accounting, evidence and resources are named records. `from_wire` validates the
234
+ canonical schema and semantic bounds; `to_wire` produces plain JSON-compatible
235
+ data. View records provide the same conversion boundary.
236
+
237
+ The normative specifications are the packaged
238
+ [View contract](https://github.com/Blockether/vis/blob/main/packages/vis-contract/resources/vis-contract/view.json),
239
+ [View schema](https://github.com/Blockether/vis/blob/main/packages/vis-contract/resources/vis-contract/schema/view.json),
240
+ [Activity contract](https://github.com/Blockether/vis/blob/main/packages/vis-contract/resources/vis-contract/activity.json)
241
+ and [Activity schema](https://github.com/Blockether/vis/blob/main/packages/vis-contract/resources/vis-contract/schema/activity.json).
242
+ SDK, engine, TUI and Companion test against fixtures owned by that contract package,
243
+ not separately maintained examples. Retired Activity envelopes are rejected; there
244
+ is no compatibility renderer or alternate import alias.
245
+
246
+ ## Remote gateway client
247
+
248
+ ```python
249
+ import os
250
+ from blockether.vis.engine import GatewayClient
251
+
252
+ with GatewayClient(
253
+ "https://gateway.example.com", token=os.environ["VIS_TOKEN"]
254
+ ) as client:
255
+ session = client.create_session(title="Python API")
256
+ turn = session.send("Describe this project")
257
+ result = turn.wait(timeout=120)
258
+ ```
259
+
260
+ The client uses an explicit origin; it does not discover credentials, download Vis,
261
+ start a gateway or stop a user's server. Closing releases its client lease, not
262
+ sessions. Idle clients renew their lease every 30 seconds using the canonical
263
+ bounded keepalive request. A failed renewal invalidates the client; close it and
264
+ create a new instance explicitly. Mutations are never replayed to regain a lease.
265
+ This client is not an embedded/local engine.
266
+
267
+ - `create_session`, `list_sessions` (one cursor page), `session(id)`.
268
+ - Session: `read`, `update`, `delete`, `send`, `input_views`, `live_views`,
269
+ `answer`, `view_action`, `events`, `turns`, `artifacts`, `transcript`, `upload`,
270
+ `download_attachment`.
271
+ - Turn: `read`, `wait`, `cancel`. Failed/cancelled turns are returned with their
272
+ status; a wait timeout does not cancel the operation.
273
+ - Dedicated methods cover non-streaming SDK operations: for example `get_models()`,
274
+ `get_provider_status(provider_id)`, `get_projects()` and `get_settings()`.
275
+ There is no public `call`. Each method's docstring names its canonical operation;
276
+ JSON operations are annotated `JSONValue`; binary/text operations return
277
+ `Response(status, content, headers)`, and empty responses return `None`.
278
+ Endpoint payloads without a canonical schema remain JSON rather than invented models.
279
+ Session SSE uses `session.events()`. `speech_events(job_id)`, `voice_events(job_id)`
280
+ and their `session_speech_events(sid, job_id)` / `session_voice_events(sid, job_id)`
281
+ counterparts expose all four job streams as typed `JobEvent` snapshots.
282
+ Job streams have no cursor: reconnect rereads state, identical snapshots are
283
+ suppressed, and `is_done` terminates the iterator. Buffered methods refuse SSE.
284
+ - `events(cursor=0, reconnects=3, retry_delay=0.2)` is a context-managed SSE
285
+ iterator returning named `Event` records with `type` and `seq`. Replay suppresses
286
+ duplicates and honors a `subscription.ready` cursor reset after restart.
287
+ Exhausting the reconnect budget raises `TransportError`.
288
+ - `GatewayError` exposes HTTP `status` and `code`; `ProtocolError` reports
289
+ incompatible/malformed protocol data; `VisTimeout` is a `TransportError`.
290
+ Mutations are never retried automatically. Keep the idempotency key if you
291
+ explicitly retry a submission whose outcome is unknown.
292
+
293
+ Use one calling thread per client. Always close event iterators. Requests and
294
+ SSE idle reads use the client's timeout (30 seconds by default); turns have a
295
+ separate wait deadline. TLS verification stays enabled and redirects are refused.
296
+
297
+ ## Owned local engine
298
+
299
+ ```python
300
+ from blockether.vis.engine import LocalEngine
301
+
302
+ with LocalEngine(executable="/path/to/vis-agent", root="/path/to/project") as engine:
303
+ session = engine.create_session(title="Local Python API")
304
+ print(session.read())
305
+ ```
306
+
307
+ Supply a compatible Vis executable that implements `sdk-stdio`. The wheel does not
308
+ bundle or download that executable, and older binaries without this command cannot
309
+ be used. `LocalEngine` starts a private engine process, not an HTTP gateway and not
310
+ an in-process JVM. Linux and macOS are supported by this transport; Windows is not.
311
+ It reuses the remote client's dedicated methods, Session, Turn and Event types.
312
+ Local session and speech/voice job events poll the same canonical resources over
313
+ stdio instead of opening SSE connections. Polling has a bounded idle timeout and
314
+ validates an event before advancing its cursor. Closing a stream never cancels a job.
315
+ Use one calling thread per engine. Request timeouts close the owned process to
316
+ prevent a late response being mistaken for a later request. `startup_timeout`
317
+ defaults to 120 seconds; individual requests default to 30 seconds.
318
+
319
+ The database is temporary and discarded on close. Engine sessions are not durable
320
+ across context-manager exits. Project configuration is inherited; agent execution
321
+ still needs a configured provider. No gateway discovery or user-server shutdown is
322
+ performed.
323
+
324
+ The integration suite runs both transports against real Vis processes and a
325
+ loopback model double, covering extension execution, input/live View, Activity,
326
+ completion, cancellation and cleanup. Select it with `VIS_TEST_LOCAL_COMMAND`.
327
+ No real-model credentials are required. Release verification and any remaining
328
+ external gates are recorded in `PLAN.md`; unit tests do not prove a linked binary.
329
+
330
+ ## Repository SDK checks
331
+
332
+ This checkout ships `.vis/extensions/sdk_checks.py` as the `sdk` extension. After
333
+ `/reload`, call its namespaced tool from a Vis session:
334
+
335
+ ```python
336
+ report = sdk.check(
337
+ root=".",
338
+ python="/path/to/verification-venv/bin/python",
339
+ engine_command="/path/to/staged/vis-agent",
340
+ )
341
+ print(report.is_pass, report.is_engine_checked)
342
+ for step in report.steps:
343
+ print(step.name, step.exit_code, step.duration_ms)
344
+ ```
345
+
346
+ The selected Python needs `pytest`, `build`, `ruff` and `twine`. Checks cover source
347
+ lint/format/tests, direct versus sdist-rebuilt wheels, exact packaged source and
348
+ canonical contracts, strict distribution metadata, documented examples/file links,
349
+ and tests of a disposable wheel installation **outside the checkout**. The check
350
+ publishes ordered Activity progress and returns frozen `CheckReport`/`CheckResult`
351
+ records with bounded per-process output tails. It stops at the first failure and
352
+ cleans up its own processes and temporary environments, including on interruption.
353
+
354
+ Omit `engine_command` for local/package checks; then `is_engine_checked` is false
355
+ and opt-in engine tests are skipped. An explicit command exercises real HTTP/stdio
356
+ with an isolated engine and local model double—not your live gateway. A staged
357
+ native wrapper tests its bundled Python runtime as well. This tool never builds
358
+ native images, spends real-model tokens, commits, tags or publishes.
359
+
360
+ ## Distribution and publishing
361
+
362
+ The SDK builds a wheel from its sdist and is tested after installation outside
363
+ the checkout. CI covers CPython 3.11–3.14 and PyPy 3.11 on Linux/macOS;
364
+ the engine's embedded interpreter is independently owned by `vis-python-runtime`.
365
+ The pinned runtime v0.5.0 is published on GitHub with Linux/macOS x64/arm64 assets.
366
+ It is not a second Python SDK or a PyPI alias.
367
+
368
+ `.github/workflows/python-publish.yml` is an explicit, version-checked publishing
369
+ gate, dependent on distribution and real-engine tests. Publishing requires the
370
+ protected `pypi` environment and a trusted publisher for `vis-agent`. Building
371
+ a wheel or adding this workflow does not publish the SDK to PyPI.
372
+
373
+ ## Where the real documentation lives
374
+
375
+ `vis.ask`, the field builders, `vis.Extension`/`vis.register`, hooks, providers and network
376
+ filters are documented where they are defined, in `blockether/vis/extension.py`, and in the
377
+ Vis docs (`doc("extending")` inside a session). Canonical JSON documents live in
378
+ `vis-contract`; this package implements their host and gateway contracts.
379
+
380
+ Apache-2.0. Part of the Vis repository: `packages/vis-agent`.