iris-pex-embedded-python 4.1.0b1__py3-none-any.whl → 4.1.1__py3-none-any.whl
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.
- iop/ai/__init__.py +9 -0
- iop/ai/guidance/instructions.md +72 -0
- iop/ai/guidance/testing.md +48 -0
- iop/ai/installer.py +186 -0
- iop/ai/skills/build-iop-app/SKILL.md +54 -0
- iop/ai/skills/build-iop-app/references/agent-guidance.md +69 -0
- iop/ai/skills/build-iop-app/references/cookbooks/add-business-operation.md +130 -0
- iop/ai/skills/build-iop-app/references/cookbooks/add-business-process.md +115 -0
- iop/ai/skills/build-iop-app/references/cookbooks/add-polling-service.md +100 -0
- iop/ai/skills/build-iop-app/references/cookbooks/code-index.md +104 -0
- iop/ai/skills/build-iop-app/references/cookbooks/fhir-submission-python-client.md +83 -0
- iop/ai/skills/build-iop-app/references/cookbooks/hello-world-production.md +103 -0
- iop/ai/skills/build-iop-app/references/cookbooks/hl7v2-native-input.md +70 -0
- iop/ai/skills/build-iop-app/references/cookbooks/hl7v2-to-fhir-with-fhir-converter.md +82 -0
- iop/ai/skills/build-iop-app/references/cookbooks/index.md +89 -0
- iop/ai/skills/build-iop-app/references/cookbooks/ingestion-pipeline.md +103 -0
- iop/ai/skills/build-iop-app/references/cookbooks/production-settings-and-targets.md +94 -0
- iop/ai/skills/build-iop-app/references/cookbooks/remote-migration.md +72 -0
- iop/ai/skills/build-iop-app/references/getting-started/register-component.md +277 -0
- iop/ai/skills/build-iop-app/references/healthcare-ai-coding.md +204 -0
- iop/ai/skills/build-iop-app/references/instructions.md +27 -0
- iop/ai/skills/build-iop-app/references/production-change-workflow.md +192 -0
- iop/ai/skills/validate-iop-app/SKILL.md +44 -0
- iop/ai/skills/validate-iop-app/references/testing.md +45 -0
- iop/cli/main.py +17 -0
- iop/cli/parser.py +32 -2
- iop/cli/types.py +3 -0
- iop/cls/IOP/Common.cls +32 -30
- iop/cls/IOP/Service/Remote/Rest/v1.cls +5 -1
- iop/components/async_request.py +1 -2
- iop/components/business_host.py +85 -77
- iop/components/business_process.py +134 -132
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/METADATA +38 -2
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/RECORD +38 -14
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/entry_points.txt +0 -0
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/licenses/LICENSE +0 -0
- {iris_pex_embedded_python-4.1.0b1.dist-info → iris_pex_embedded_python-4.1.1.dist-info}/top_level.txt +0 -0
iop/ai/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# IoP Application Guidance
|
|
2
|
+
|
|
3
|
+
This repository contains an IoP application. IoP means Interoperability On
|
|
4
|
+
Python: a Python-first framework for building InterSystems IRIS and Health
|
|
5
|
+
Connect interoperability productions.
|
|
6
|
+
|
|
7
|
+
## Start With The Application
|
|
8
|
+
|
|
9
|
+
Before a major change, establish the business goal, inbound and outbound
|
|
10
|
+
systems, protocols or standards, routing and transformation behavior, runtime
|
|
11
|
+
constraints, and acceptance criteria. Infer facts from the repository before
|
|
12
|
+
asking the user.
|
|
13
|
+
|
|
14
|
+
Read the project README, `settings.py`, the production graph, message and
|
|
15
|
+
component modules, tests, fixtures, sample payloads, dependency files, and
|
|
16
|
+
available Docker or Compose lifecycle. Use the bundled `build-iop-app` skill
|
|
17
|
+
for implementation workflows and `validate-iop-app` for verification.
|
|
18
|
+
|
|
19
|
+
## IoP Rules
|
|
20
|
+
|
|
21
|
+
- Prefer a Python `Production` exported through `PRODUCTIONS`.
|
|
22
|
+
- Declare topology with `prod.service(...)`, `prod.process(...)`,
|
|
23
|
+
`prod.operation(...)`, and graph connections.
|
|
24
|
+
- Declare configurable outbound routes with `target()` and connect them through
|
|
25
|
+
the production graph.
|
|
26
|
+
- Communicate between production components through messages and configured
|
|
27
|
+
targets. Do not instantiate another production component directly.
|
|
28
|
+
- Use `on_init()` for startup and `on_tear_down()` for cleanup. Do not put
|
|
29
|
+
component startup logic in `__init__()`.
|
|
30
|
+
- Use `@dataclass` on regular `Message` classes. Do not add `@dataclass` to
|
|
31
|
+
`PydanticMessage` classes.
|
|
32
|
+
- Use `PersistentMessage` only when IRIS requires a native persistent message
|
|
33
|
+
body.
|
|
34
|
+
- Prefer typed one-argument handlers or `@handler(MessageType)`. Use
|
|
35
|
+
`on_message(self, request)` as a simple fallback.
|
|
36
|
+
- Avoid raw `CLASSES` entries for components already declared in a production
|
|
37
|
+
graph. Keep them for standalone bindings and legacy migrations.
|
|
38
|
+
- Keep executable migration examples behind `if __name__ == "__main__":` so
|
|
39
|
+
`PRODUCTIONS` can be imported safely.
|
|
40
|
+
|
|
41
|
+
## Project Imports
|
|
42
|
+
|
|
43
|
+
Treat the directory containing `settings.py` as the project import root. Use
|
|
44
|
+
modules reachable relative to that file. Fix package layout or imports instead
|
|
45
|
+
of modifying `PYTHONPATH`, `os.environ["PYTHONPATH"]`, or global `sys.path` in
|
|
46
|
+
application code.
|
|
47
|
+
|
|
48
|
+
## Production Design
|
|
49
|
+
|
|
50
|
+
- Business Services are inbound entry points and triggers. A polling service
|
|
51
|
+
owns acquisition from the source it polls, including an HTTP, file, or
|
|
52
|
+
database read, and emits a data-bearing message.
|
|
53
|
+
- Business Processes own routing, decisions, transformations, and orchestration.
|
|
54
|
+
- Business Operations isolate destination side effects such as persistence,
|
|
55
|
+
file writes, submissions, and API calls made after processing. An API lookup
|
|
56
|
+
requested by a process is also an operation; acquisition by a polling service
|
|
57
|
+
from its configured source remains service work.
|
|
58
|
+
- Model a complete source-to-destination ingestion flow as `service -> process
|
|
59
|
+
-> operation` by default. Omit the process only for a genuinely trivial
|
|
60
|
+
pass-through and state why no orchestration boundary is needed.
|
|
61
|
+
- Prefer native IRIS or Health Connect components for established healthcare
|
|
62
|
+
standards and transports, then use Python for application-specific logic.
|
|
63
|
+
|
|
64
|
+
For an existing or shared IRIS production, inspect and plan changes before
|
|
65
|
+
mutation. Use the conservative plan, review, apply, verify, and backup workflow;
|
|
66
|
+
do not treat an imported graph as a lossless reconstruction of deployed intent.
|
|
67
|
+
|
|
68
|
+
## Completion
|
|
69
|
+
|
|
70
|
+
Add or update focused tests, declare direct dependencies, and preserve working
|
|
71
|
+
project lifecycle automation when behavior changes. Run the `validate-iop-app`
|
|
72
|
+
skill and report commands, results, and remaining runtime verification clearly.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Testing IoP Applications
|
|
2
|
+
|
|
3
|
+
Run the fastest relevant checks first from the project root.
|
|
4
|
+
|
|
5
|
+
## Local Checks
|
|
6
|
+
|
|
7
|
+
1. Run the project's Python tests:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m pytest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Validate the migration without writing to IRIS:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
iop --migrate settings.py --dry-run --strict-production-validation
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Resolve import errors through package layout and imports, not `PYTHONPATH`
|
|
20
|
+
changes in application code.
|
|
21
|
+
|
|
22
|
+
## Runtime Checks
|
|
23
|
+
|
|
24
|
+
Use a repository-owned disposable Docker or Compose environment for runtime
|
|
25
|
+
verification when it is available. Only mutate a shared, remote, or otherwise
|
|
26
|
+
non-disposable IRIS environment when the user requests it and the target is
|
|
27
|
+
known. Verify at least one observable result:
|
|
28
|
+
|
|
29
|
+
- production and component status;
|
|
30
|
+
- expected output file, API request, database write, or FHIR resource;
|
|
31
|
+
- production logs without blocking errors;
|
|
32
|
+
- Message Viewer flow or queue state.
|
|
33
|
+
|
|
34
|
+
Container health is not production health. Confirm initialization and migration
|
|
35
|
+
completed, use `iop --status` to verify the intended production is running, then
|
|
36
|
+
trigger or wait for a Business Service and inspect an actual destination effect.
|
|
37
|
+
Prefer `iop` commands for migration, start, status, logs, and queues. Do not use
|
|
38
|
+
an ObjectScript terminal when the IoP CLI supports the operation. Automated
|
|
39
|
+
checks must return control: use `iop --start <production-name> --detach` and a
|
|
40
|
+
finite log snapshot such as `iop --log 50`. Bare `--start` and bare `--log`
|
|
41
|
+
stream logs until interrupted.
|
|
42
|
+
|
|
43
|
+
Do not use `iop --test` as the normal way to test a Business Service. Use the
|
|
44
|
+
production runtime or director path so deployed settings, targets, and runtime
|
|
45
|
+
context are active.
|
|
46
|
+
|
|
47
|
+
For shared or brownfield productions, use a reviewed production change plan and
|
|
48
|
+
backup rather than full registration by default.
|
iop/ai/installer.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
AGENTS = ("codex", "claude", "gemini")
|
|
9
|
+
MANAGED_START = "<!-- iop-agent-guidance:start -->"
|
|
10
|
+
MANAGED_END = "<!-- iop-agent-guidance:end -->"
|
|
11
|
+
|
|
12
|
+
_AGENT_PATHS = {
|
|
13
|
+
"codex": ("AGENTS.md", ".codex/skills"),
|
|
14
|
+
"claude": ("CLAUDE.md", ".claude/skills"),
|
|
15
|
+
"gemini": ("GEMINI.md", ".gemini/skills"),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_SKILL_DESCRIPTIONS = {
|
|
19
|
+
"build-iop-app": (
|
|
20
|
+
"Build or modify an IoP application using production graphs, messages, "
|
|
21
|
+
"components, tests, and all applicable bundled cookbooks. Use for new or "
|
|
22
|
+
"changed Business Services, Business Processes, Business Operations, "
|
|
23
|
+
"routes, settings, healthcare flows, or complete productions."
|
|
24
|
+
),
|
|
25
|
+
"validate-iop-app": (
|
|
26
|
+
"Validate an IoP application with unit tests, strict migration dry-run, "
|
|
27
|
+
"and container-backed IRIS runtime checks when available. Use after changing "
|
|
28
|
+
"messages, components, production topology, settings, migration files, "
|
|
29
|
+
"container lifecycle, or runtime behavior."
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentGuidanceConflictError(ValueError):
|
|
35
|
+
"""Raised when installed framework-owned guidance would be overwritten."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, paths: Iterable[Path]):
|
|
38
|
+
self.paths = tuple(paths)
|
|
39
|
+
joined = ", ".join(str(path) for path in self.paths)
|
|
40
|
+
super().__init__(
|
|
41
|
+
"agent guidance conflicts with existing files: "
|
|
42
|
+
f"{joined}. Re-run with --force-agent-guidance to replace them."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class InstallResult:
|
|
48
|
+
target: Path
|
|
49
|
+
agents: tuple[str, ...]
|
|
50
|
+
written: tuple[Path, ...]
|
|
51
|
+
unchanged: tuple[Path, ...]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def install_agent_guidance(
|
|
55
|
+
target: str | Path = ".",
|
|
56
|
+
*,
|
|
57
|
+
agents: Iterable[str] | None = None,
|
|
58
|
+
force: bool = False,
|
|
59
|
+
) -> InstallResult:
|
|
60
|
+
"""Install version-matched IoP guidance into an application repository."""
|
|
61
|
+
target_path = Path(target).expanduser().resolve()
|
|
62
|
+
selected_agents = _normalize_agents(agents)
|
|
63
|
+
owned_files = _owned_file_manifest(target_path, selected_agents)
|
|
64
|
+
root_files = _root_file_manifest(target_path, selected_agents)
|
|
65
|
+
|
|
66
|
+
conflicts = [
|
|
67
|
+
path
|
|
68
|
+
for path, content in owned_files.items()
|
|
69
|
+
if path.exists() and path.read_bytes() != content
|
|
70
|
+
]
|
|
71
|
+
if conflicts and not force:
|
|
72
|
+
raise AgentGuidanceConflictError(conflicts)
|
|
73
|
+
|
|
74
|
+
manifest = {**owned_files, **root_files}
|
|
75
|
+
written: list[Path] = []
|
|
76
|
+
unchanged: list[Path] = []
|
|
77
|
+
for path, content in manifest.items():
|
|
78
|
+
if path.exists() and path.read_bytes() == content:
|
|
79
|
+
unchanged.append(path)
|
|
80
|
+
continue
|
|
81
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
path.write_bytes(content)
|
|
83
|
+
written.append(path)
|
|
84
|
+
|
|
85
|
+
return InstallResult(
|
|
86
|
+
target=target_path,
|
|
87
|
+
agents=selected_agents,
|
|
88
|
+
written=tuple(written),
|
|
89
|
+
unchanged=tuple(unchanged),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _normalize_agents(agents: Iterable[str] | None) -> tuple[str, ...]:
|
|
94
|
+
values = tuple(agents or AGENTS)
|
|
95
|
+
invalid = sorted(set(values) - set(AGENTS))
|
|
96
|
+
if invalid:
|
|
97
|
+
raise ValueError(f"unsupported agents: {', '.join(invalid)}")
|
|
98
|
+
return tuple(agent for agent in AGENTS if agent in values)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _owned_file_manifest(
|
|
102
|
+
target: Path,
|
|
103
|
+
agents: tuple[str, ...],
|
|
104
|
+
) -> dict[Path, bytes]:
|
|
105
|
+
manifest: dict[Path, bytes] = {}
|
|
106
|
+
resource_root = files("iop.ai")
|
|
107
|
+
for resource_dir, destination in (
|
|
108
|
+
(resource_root.joinpath("guidance"), target / ".config" / "AGENTS"),
|
|
109
|
+
(
|
|
110
|
+
resource_root.joinpath("skills"),
|
|
111
|
+
target / ".config" / "AGENTS" / "skills",
|
|
112
|
+
),
|
|
113
|
+
):
|
|
114
|
+
_add_resource_tree(manifest, resource_dir, destination)
|
|
115
|
+
|
|
116
|
+
for agent in agents:
|
|
117
|
+
_, skills_root = _AGENT_PATHS[agent]
|
|
118
|
+
for skill_name, description in _SKILL_DESCRIPTIONS.items():
|
|
119
|
+
wrapper = _skill_wrapper(skill_name, description)
|
|
120
|
+
path = target / skills_root / skill_name / "SKILL.md"
|
|
121
|
+
manifest[path] = wrapper.encode("utf-8")
|
|
122
|
+
return manifest
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _root_file_manifest(
|
|
126
|
+
target: Path,
|
|
127
|
+
agents: tuple[str, ...],
|
|
128
|
+
) -> dict[Path, bytes]:
|
|
129
|
+
manifest: dict[Path, bytes] = {}
|
|
130
|
+
for agent in agents:
|
|
131
|
+
root_name, _ = _AGENT_PATHS[agent]
|
|
132
|
+
path = target / root_name
|
|
133
|
+
current = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
134
|
+
merged = _merge_managed_block(current, _root_block(agent))
|
|
135
|
+
manifest[path] = merged.encode("utf-8")
|
|
136
|
+
return manifest
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _add_resource_tree(manifest, resource, destination: Path) -> None:
|
|
140
|
+
for child in resource.iterdir():
|
|
141
|
+
child_destination = destination / child.name
|
|
142
|
+
if child.is_dir():
|
|
143
|
+
_add_resource_tree(manifest, child, child_destination)
|
|
144
|
+
elif child.name != "__pycache__":
|
|
145
|
+
manifest[child_destination] = child.read_bytes()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _merge_managed_block(content: str, block: str) -> str:
|
|
149
|
+
managed = f"{MANAGED_START}\n{block.rstrip()}\n{MANAGED_END}"
|
|
150
|
+
start = content.find(MANAGED_START)
|
|
151
|
+
end = content.find(MANAGED_END)
|
|
152
|
+
if start >= 0 and end >= start:
|
|
153
|
+
end += len(MANAGED_END)
|
|
154
|
+
return f"{content[:start]}{managed}{content[end:]}"
|
|
155
|
+
if not content.strip():
|
|
156
|
+
return f"{managed}\n"
|
|
157
|
+
return f"{content.rstrip()}\n\n{managed}\n"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _root_block(agent: str) -> str:
|
|
161
|
+
import_line = {
|
|
162
|
+
"codex": "Read `.config/AGENTS/instructions.md` before changing code.",
|
|
163
|
+
"claude": "@.config/AGENTS/instructions.md",
|
|
164
|
+
"gemini": "@./.config/AGENTS/instructions.md",
|
|
165
|
+
}[agent]
|
|
166
|
+
return "\n".join(
|
|
167
|
+
(
|
|
168
|
+
"# IoP Agent Guidance",
|
|
169
|
+
"",
|
|
170
|
+
"This repository contains an IoP application.",
|
|
171
|
+
import_line,
|
|
172
|
+
"Use `build-iop-app` for application changes.",
|
|
173
|
+
"Use `validate-iop-app` for verification.",
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _skill_wrapper(skill_name: str, description: str) -> str:
|
|
179
|
+
return (
|
|
180
|
+
"---\n"
|
|
181
|
+
f"name: {skill_name}\n"
|
|
182
|
+
f"description: {description}\n"
|
|
183
|
+
"---\n\n"
|
|
184
|
+
f"Read and follow `.config/AGENTS/skills/{skill_name}/SKILL.md`. "
|
|
185
|
+
"Load its referenced files only when the workflow calls for them.\n"
|
|
186
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-iop-app
|
|
3
|
+
description: Build or modify an IoP application using production graphs, messages, components, tests, and all applicable bundled cookbooks. Use for new or changed Business Services, Business Processes, Business Operations, routes, settings, healthcare flows, or complete productions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build An IoP Application
|
|
7
|
+
|
|
8
|
+
Read `references/instructions.md`, then inspect the project README,
|
|
9
|
+
`settings.py`, production graph, messages, components, tests, samples,
|
|
10
|
+
dependency manifests, and Docker or Compose lifecycle files.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Establish the business goal, inbound and outbound systems, protocols,
|
|
15
|
+
routing or transformation behavior, runtime constraints, and acceptance
|
|
16
|
+
criteria. Infer repository facts before asking questions.
|
|
17
|
+
2. Start with `references/cookbooks/index.md` and read every cookbook implicated
|
|
18
|
+
by the requested flow. Complete ingestion work normally requires the
|
|
19
|
+
ingestion-pipeline, polling-service, process, and operation cookbooks. Use
|
|
20
|
+
`code-index.md` when starting from source symbols.
|
|
21
|
+
3. Write a short component-role plan before coding: identify the source
|
|
22
|
+
acquisition service, orchestration process, destination operations, messages,
|
|
23
|
+
and graph edges. A polling service fetches from its configured source and
|
|
24
|
+
emits data; a process validates, transforms, and routes it; an operation
|
|
25
|
+
performs persistence or another destination effect. Model complete ingestion
|
|
26
|
+
as `service -> process -> operation` unless a trivial pass-through is
|
|
27
|
+
explicitly justified.
|
|
28
|
+
4. Implement through public `iop` imports. Use `target()` and graph connections
|
|
29
|
+
instead of direct component calls. Use `on_init()`, not `__init__()`, for
|
|
30
|
+
startup.
|
|
31
|
+
5. Add focused tests and representative samples for each component boundary and
|
|
32
|
+
the production topology. Declare every directly imported third-party package
|
|
33
|
+
in the project's dependency manifest.
|
|
34
|
+
6. Ensure the migration entrypoint exports the desired production through
|
|
35
|
+
`PRODUCTIONS` and remains safe to import. Preserve existing container
|
|
36
|
+
initialization, migration, health, and startup automation; update production
|
|
37
|
+
paths and names instead of disabling that lifecycle.
|
|
38
|
+
7. Run the `validate-iop-app` skill. Do not deploy unless the user explicitly
|
|
39
|
+
requests it. A repository-owned disposable container is a validation
|
|
40
|
+
environment, not a shared deployment.
|
|
41
|
+
|
|
42
|
+
For healthcare work, prefer native IRIS or Health Connect standards and
|
|
43
|
+
transport components. Use the healthcare cookbooks for HL7v2 and FHIR choices.
|
|
44
|
+
|
|
45
|
+
For an existing shared production, read the production change workflow and use
|
|
46
|
+
plan/review/apply/verify instead of assuming the Python graph is a complete
|
|
47
|
+
deployed-state replacement.
|
|
48
|
+
|
|
49
|
+
## References
|
|
50
|
+
|
|
51
|
+
- Core IoP rules: `references/instructions.md`
|
|
52
|
+
- Task workflows: `references/cookbooks/index.md`
|
|
53
|
+
- Existing production safety: `references/production-change-workflow.md`
|
|
54
|
+
- Healthcare design: `references/healthcare-ai-coding.md`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# IoP Agent Guidance And Skills
|
|
2
|
+
|
|
3
|
+
IoP ships version-matched project guidance, Agent Skills, and offline copies of
|
|
4
|
+
the IoP cookbooks. Install them in any IoP application repository, including a
|
|
5
|
+
project that was not created from the IoP template:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
iop --install-agent-guidance
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The command configures Codex, Claude Code, and Gemini CLI by default. It adds a
|
|
12
|
+
small IoP-managed block to existing root instruction files without replacing
|
|
13
|
+
other project rules, installs canonical guidance in `.config/AGENTS`, and adds
|
|
14
|
+
the `build-iop-app` and `validate-iop-app` skills for each agent.
|
|
15
|
+
|
|
16
|
+
## Select Agents
|
|
17
|
+
|
|
18
|
+
Use one or more `--agent` options when the project only needs specific clients:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
iop --install-agent-guidance --agent codex
|
|
22
|
+
iop --install-agent-guidance --agent claude --agent gemini
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Pass a project directory to configure a repository other than the current one:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
iop --install-agent-guidance /path/to/iop-application
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The installer is idempotent. If an IoP-managed file was changed locally, it
|
|
32
|
+
stops before writing anything. Review the conflict, then replace only the
|
|
33
|
+
IoP-managed snapshot when appropriate:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
iop --install-agent-guidance --force-agent-guidance
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Rerun that command after upgrading IoP to refresh the guides, skills, and
|
|
40
|
+
offline cookbooks to the installed framework version.
|
|
41
|
+
|
|
42
|
+
## Install Skills Directly
|
|
43
|
+
|
|
44
|
+
Developers who only need the portable skills can install them from the IoP
|
|
45
|
+
repository with the cross-agent Skills CLI:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx skills add \
|
|
49
|
+
https://github.com/grongierisc/interoperability-embedded-python/tree/master/src/iop/ai/skills \
|
|
50
|
+
--skill build-iop-app \
|
|
51
|
+
--skill validate-iop-app
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Direct installation provides the self-contained skills and their references.
|
|
55
|
+
It does not add the always-on `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md` project
|
|
56
|
+
guidance; use the IoP installer when those entrypoints are wanted.
|
|
57
|
+
|
|
58
|
+
## Skill Responsibilities
|
|
59
|
+
|
|
60
|
+
- `build-iop-app` inspects the application, selects the relevant bundled
|
|
61
|
+
cookbook, and guides message, component, production graph, sample, and test
|
|
62
|
+
changes.
|
|
63
|
+
- `validate-iop-app` runs project tests and strict migration dry-run first, then
|
|
64
|
+
requires container-backed runtime verification when the project provides a
|
|
65
|
+
disposable environment, while protecting shared or remote IRIS instances.
|
|
66
|
+
|
|
67
|
+
The skill files use the shared Agent Skills `SKILL.md` format with only portable
|
|
68
|
+
frontmatter. Client-specific wrappers contain no duplicated IoP policy; the
|
|
69
|
+
installed `.config/AGENTS` snapshot remains the local source of truth.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Cookbook: Add A BusinessOperation
|
|
2
|
+
|
|
3
|
+
## When To Use This
|
|
4
|
+
|
|
5
|
+
Use this cookbook when a production needs a destination side-effect boundary:
|
|
6
|
+
calling a downstream API, writing a file, submitting FHIR, storing data, or
|
|
7
|
+
wrapping a technical operation behind a production message. Acquisition from
|
|
8
|
+
the configured source of a `PollingBusinessService` remains in that service;
|
|
9
|
+
the operation receives the acquired or processed data.
|
|
10
|
+
|
|
11
|
+
## Files You Will Touch
|
|
12
|
+
|
|
13
|
+
- the operation module, such as `bo.py` or `operations.py`
|
|
14
|
+
- the message module, such as `msg.py` or `messages.py`
|
|
15
|
+
- `settings.py`
|
|
16
|
+
- the fastest relevant test or sample payload
|
|
17
|
+
|
|
18
|
+
## Prompt To Give Your Agent
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Add a new IoP BusinessOperation to this project.
|
|
22
|
+
|
|
23
|
+
Business goal:
|
|
24
|
+
<describe what the operation receives, validates, logs, transforms, or sends>
|
|
25
|
+
|
|
26
|
+
Implementation requirements:
|
|
27
|
+
- Reuse existing message classes if they already fit.
|
|
28
|
+
- If a new message is needed, define it as a Message dataclass unless there is
|
|
29
|
+
a specific need for PersistentMessage.
|
|
30
|
+
- Implement a fallback on_message(self, request) for simple operations, or route
|
|
31
|
+
by message type with typed one-argument methods or the @handler decorator.
|
|
32
|
+
- Return a response message or the original request when that matches the flow.
|
|
33
|
+
- Use self.log_info(), self.log_warning(), or self.log_error() for component
|
|
34
|
+
logging.
|
|
35
|
+
- Do not add startup work to __init__(); use on_init() only if startup work is
|
|
36
|
+
required.
|
|
37
|
+
- Update settings.py so the operation is added to the Production graph.
|
|
38
|
+
- Add or update the fastest relevant test.
|
|
39
|
+
- Show the exact verification command.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Expected Implementation
|
|
43
|
+
|
|
44
|
+
A simple operation can use `on_message()`:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from iop import BusinessOperation
|
|
48
|
+
|
|
49
|
+
from messages import OrderRequest, OrderResponse
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OrderOperation(BusinessOperation):
|
|
53
|
+
def on_message(self, request: OrderRequest) -> OrderResponse:
|
|
54
|
+
self.log_info(f"Processing order {request.order_id}")
|
|
55
|
+
return OrderResponse(order_id=request.order_id, status="accepted")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
When one operation handles multiple message types, prefer typed one-argument
|
|
59
|
+
methods or the `@handler` decorator:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from iop import BusinessOperation, handler
|
|
63
|
+
|
|
64
|
+
from messages import CancelOrder, OrderRequest, OrderResponse
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class OrderOperation(BusinessOperation):
|
|
68
|
+
def on_message(self, request):
|
|
69
|
+
self.log_warning(f"Unhandled message {type(request).__name__}")
|
|
70
|
+
return request
|
|
71
|
+
|
|
72
|
+
def submit_order(self, request: OrderRequest) -> OrderResponse:
|
|
73
|
+
self.log_info(f"Submitting order {request.order_id}")
|
|
74
|
+
return OrderResponse(order_id=request.order_id, status="accepted")
|
|
75
|
+
|
|
76
|
+
@handler(CancelOrder)
|
|
77
|
+
def cancel_order(self, request):
|
|
78
|
+
self.log_info(f"Cancelling order {request.order_id}")
|
|
79
|
+
return request
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
IoP dispatches to:
|
|
83
|
+
|
|
84
|
+
- a method decorated with `@handler(MessageType)` first
|
|
85
|
+
- a typed one-argument method such as `submit_order(self, request: OrderRequest)`
|
|
86
|
+
- `on_message(self, request)` as the fallback
|
|
87
|
+
|
|
88
|
+
The production graph should register the operation:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
operation = prod.operation("OrderOperation", OrderOperation)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If another component sends to the operation, declare a `target()` setting on the
|
|
95
|
+
sender and connect it:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
process = prod.process("OrderProcess", OrderProcess)
|
|
99
|
+
process.connect(OrderProcess.Orders, operation)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Migration Command
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
iop --migrate settings.py --dry-run
|
|
106
|
+
iop --migrate settings.py
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Verification
|
|
110
|
+
|
|
111
|
+
Use the fastest local test first:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python -m pytest
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
If there is no existing test suite, ask the agent to add a small test for the
|
|
118
|
+
pure Python transformation or validation logic.
|
|
119
|
+
|
|
120
|
+
## Common Mistakes
|
|
121
|
+
|
|
122
|
+
- Creating an operation but not adding it to `settings.py`.
|
|
123
|
+
- Combining source acquisition, orchestration, and destination persistence in
|
|
124
|
+
one operation.
|
|
125
|
+
- Calling another production component as a normal Python object.
|
|
126
|
+
- Hiding connection names in strings instead of using `target()` and
|
|
127
|
+
`prod.connect(...)`.
|
|
128
|
+
- Using `PersistentMessage` when a regular `Message` dataclass is enough.
|
|
129
|
+
- Adding multiple handlers for the same message type without making the
|
|
130
|
+
intended precedence explicit.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Cookbook: Add A BusinessProcess
|
|
2
|
+
|
|
3
|
+
## When To Use This
|
|
4
|
+
|
|
5
|
+
Use this cookbook when a production needs routing, orchestration, decision
|
|
6
|
+
logic, enrichment, or coordination between services and operations.
|
|
7
|
+
|
|
8
|
+
Business Processes should not own external side effects directly when a
|
|
9
|
+
Business Operation is the better boundary. Keep the process focused on deciding
|
|
10
|
+
what happens next and sending messages to downstream targets.
|
|
11
|
+
|
|
12
|
+
## Files You Will Touch
|
|
13
|
+
|
|
14
|
+
- the process module, such as `bp.py` or `processes.py`
|
|
15
|
+
- the message module, such as `msg.py` or `messages.py`
|
|
16
|
+
- operation modules only when downstream behavior is missing
|
|
17
|
+
- `settings.py`
|
|
18
|
+
- tests or sample payloads for routing decisions
|
|
19
|
+
|
|
20
|
+
## Prompt To Give Your Agent
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
Add a new IoP BusinessProcess to this project.
|
|
24
|
+
|
|
25
|
+
Business goal:
|
|
26
|
+
<describe the routing, orchestration, enrichment, or decision logic>
|
|
27
|
+
|
|
28
|
+
Implementation requirements:
|
|
29
|
+
- Reuse existing message classes if they already fit.
|
|
30
|
+
- Declare outbound routes with target() attributes on the process class.
|
|
31
|
+
- Route messages with send_request_sync(), send_request_async(), or direct
|
|
32
|
+
response behavior according to the existing project pattern.
|
|
33
|
+
- Use on_message(self, request) for simple processes, or route by message type
|
|
34
|
+
with typed one-argument methods or the @handler decorator.
|
|
35
|
+
- Keep external API calls, database writes, file writes, and FHIR submission in
|
|
36
|
+
BusinessOperation classes.
|
|
37
|
+
- Do not add startup work to __init__(); use on_init() only if startup work is
|
|
38
|
+
required.
|
|
39
|
+
- Update settings.py so the process is added to the Production graph and its
|
|
40
|
+
targets are connected.
|
|
41
|
+
- Add or update tests for routing decisions.
|
|
42
|
+
- Show the exact migration and verification commands.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Expected Implementation
|
|
46
|
+
|
|
47
|
+
A process declares outbound targets and sends messages to them:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from iop import BusinessProcess, Message, handler, target
|
|
51
|
+
|
|
52
|
+
from messages import OrderRequest, OrderResponse, RejectedOrder
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OrderProcess(BusinessProcess):
|
|
56
|
+
Accepted = target()
|
|
57
|
+
Rejected = target()
|
|
58
|
+
|
|
59
|
+
def on_message(self, request):
|
|
60
|
+
self.log_warning(f"Unhandled message {type(request).__name__}")
|
|
61
|
+
return request
|
|
62
|
+
|
|
63
|
+
def route_order(self, request: OrderRequest):
|
|
64
|
+
if not request.order_id:
|
|
65
|
+
return self.send_request_sync(self.Rejected, RejectedOrder(reason="missing id"))
|
|
66
|
+
|
|
67
|
+
return self.send_request_sync(self.Accepted, request)
|
|
68
|
+
|
|
69
|
+
@handler(RejectedOrder)
|
|
70
|
+
def route_rejected(self, request):
|
|
71
|
+
self.log_info(request.reason)
|
|
72
|
+
return request
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
IoP dispatches to:
|
|
76
|
+
|
|
77
|
+
- a method decorated with `@handler(MessageType)` first
|
|
78
|
+
- a typed one-argument method such as `route_order(self, request: OrderRequest)`
|
|
79
|
+
- `on_message(self, request)` as the fallback
|
|
80
|
+
|
|
81
|
+
Wire the process in `settings.py`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
process = prod.process("OrderProcess", OrderProcess)
|
|
85
|
+
accepted = prod.operation("AcceptedOperation", AcceptedOperation)
|
|
86
|
+
rejected = prod.operation("RejectedOperation", RejectedOperation)
|
|
87
|
+
|
|
88
|
+
process.connect(OrderProcess.Accepted, accepted)
|
|
89
|
+
process.connect(OrderProcess.Rejected, rejected)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Migration Command
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
iop --migrate settings.py --dry-run
|
|
96
|
+
iop --migrate settings.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Verification
|
|
100
|
+
|
|
101
|
+
- Unit-test the routing decision with representative messages.
|
|
102
|
+
- Dry-run migration shows the process and all target settings.
|
|
103
|
+
- The production graph contains every expected process edge.
|
|
104
|
+
- Runtime trace shows messages passing from service to process to operation.
|
|
105
|
+
|
|
106
|
+
## Common Mistakes
|
|
107
|
+
|
|
108
|
+
- Putting external API calls or database writes directly in the process.
|
|
109
|
+
- Forgetting `target()` declarations for outbound routes.
|
|
110
|
+
- Returning raw dictionaries instead of message objects when the downstream
|
|
111
|
+
component expects IoP messages.
|
|
112
|
+
- Adding multiple handlers for the same message type without making the
|
|
113
|
+
intended precedence explicit.
|
|
114
|
+
- Calling a downstream component directly instead of sending a production
|
|
115
|
+
message through a target.
|