agentixx 0.1.0__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.
- agentixx-0.1.0/.claude/settings.local.json +38 -0
- agentixx-0.1.0/.claude/skills/agent-integration/SKILL.md +378 -0
- agentixx-0.1.0/.github/workflows/docs.yml +68 -0
- agentixx-0.1.0/.github/workflows/test.yml +28 -0
- agentixx-0.1.0/.gitignore +220 -0
- agentixx-0.1.0/ARCHITECTURE.md +232 -0
- agentixx-0.1.0/CLAUDE.md +180 -0
- agentixx-0.1.0/LICENSE +21 -0
- agentixx-0.1.0/PKG-INFO +200 -0
- agentixx-0.1.0/README.md +177 -0
- agentixx-0.1.0/ROADMAP.md +118 -0
- agentixx-0.1.0/agentix/__init__.py +26 -0
- agentixx-0.1.0/agentix/cli/__init__.py +70 -0
- agentixx-0.1.0/agentix/cli/__main__.py +10 -0
- agentixx-0.1.0/agentix/cli/_resolve.py +57 -0
- agentixx-0.1.0/agentix/cli/build.py +247 -0
- agentixx-0.1.0/agentix/deployment/__init__.py +16 -0
- agentixx-0.1.0/agentix/deployment/_plugin.py +202 -0
- agentixx-0.1.0/agentix/deployment/base.py +136 -0
- agentixx-0.1.0/agentix/runtime/PROTOCOL.md +173 -0
- agentixx-0.1.0/agentix/runtime/__init__.py +19 -0
- agentixx-0.1.0/agentix/runtime/client/__init__.py +15 -0
- agentixx-0.1.0/agentix/runtime/client/client.py +471 -0
- agentixx-0.1.0/agentix/runtime/server/__init__.py +18 -0
- agentixx-0.1.0/agentix/runtime/server/app.py +110 -0
- agentixx-0.1.0/agentix/runtime/server/sio.py +373 -0
- agentixx-0.1.0/agentix/runtime/server/worker/__init__.py +5 -0
- agentixx-0.1.0/agentix/runtime/server/worker/__main__.py +6 -0
- agentixx-0.1.0/agentix/runtime/server/worker/client.py +403 -0
- agentixx-0.1.0/agentix/runtime/server/worker/invoker.py +328 -0
- agentixx-0.1.0/agentix/runtime/server/worker/process.py +296 -0
- agentixx-0.1.0/agentix/runtime/shared/__init__.py +22 -0
- agentixx-0.1.0/agentix/runtime/shared/callables.py +46 -0
- agentixx-0.1.0/agentix/runtime/shared/codec.py +102 -0
- agentixx-0.1.0/agentix/runtime/shared/events.py +35 -0
- agentixx-0.1.0/agentix/runtime/shared/frames.py +29 -0
- agentixx-0.1.0/agentix/runtime/shared/framing.py +72 -0
- agentixx-0.1.0/agentix/runtime/shared/idents.py +11 -0
- agentixx-0.1.0/agentix/runtime/shared/models.py +59 -0
- agentixx-0.1.0/agentix/runtime/shared/pump.py +47 -0
- agentixx-0.1.0/agentix/runtime/shared/rpc.py +164 -0
- agentixx-0.1.0/docs/DEPLOY.md +58 -0
- agentixx-0.1.0/docs/concepts/bundles.mdx +107 -0
- agentixx-0.1.0/docs/concepts/remote-calls.mdx +139 -0
- agentixx-0.1.0/docs/deployment.mdx +106 -0
- agentixx-0.1.0/docs/development.mdx +113 -0
- agentixx-0.1.0/docs/docs.json +59 -0
- agentixx-0.1.0/docs/index.mdx +138 -0
- agentixx-0.1.0/docs/integrate-agent.mdx +227 -0
- agentixx-0.1.0/docs/integrate-dataset.mdx +188 -0
- agentixx-0.1.0/docs/quickstart.mdx +156 -0
- agentixx-0.1.0/docs/reference/architecture.mdx +147 -0
- agentixx-0.1.0/docs/reference/cli.mdx +84 -0
- agentixx-0.1.0/pyproject.toml +75 -0
- agentixx-0.1.0/tests/__init__.py +0 -0
- agentixx-0.1.0/tests/_rpc_helpers.py +26 -0
- agentixx-0.1.0/tests/_user_app_target.py +11 -0
- agentixx-0.1.0/tests/_worker_target.py +79 -0
- agentixx-0.1.0/tests/conftest.py +133 -0
- agentixx-0.1.0/tests/test_models.py +45 -0
- agentixx-0.1.0/tests/test_plugin_axes.py +35 -0
- agentixx-0.1.0/tests/test_plugin_registry.py +160 -0
- agentixx-0.1.0/tests/test_remote_importable_module.py +38 -0
- agentixx-0.1.0/tests/test_rpc_protocol.py +216 -0
- agentixx-0.1.0/tests/test_worker_subprocess.py +112 -0
- agentixx-0.1.0/uv.lock +1448 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(chmod +x:*)",
|
|
5
|
+
"Bash(docker run:*)",
|
|
6
|
+
"Bash(mkdir -p agents/claude-code runtime scripts demo/task)",
|
|
7
|
+
"Bash(cp blackbox-agents/agents/claude-code/default.nix agents/claude-code/)",
|
|
8
|
+
"Bash(cp blackbox-agents/demo/task/Dockerfile demo/task/)",
|
|
9
|
+
"Bash(cp blackbox-agents/demo/task/instruction.md demo/task/)",
|
|
10
|
+
"Bash(rm -rf blackbox-agents src)",
|
|
11
|
+
"Read(//nix/store/d7zdxbak09kri15bn76fllf2wws1k5gq-hnix-runtime/bin/**)",
|
|
12
|
+
"Bash(/nix/store/d7zdxbak09kri15bn76fllf2wws1k5gq-hnix-runtime/bin/hnix-info /nix/store/i0w2ybaxdlv463dvr81y3a30aryhv6qm-claude-code-runtime-2.1.96)",
|
|
13
|
+
"WebSearch",
|
|
14
|
+
"WebFetch(domain:github.com)",
|
|
15
|
+
"WebFetch(domain:swe-rex.com)",
|
|
16
|
+
"Read(//nix/store/f7xa531ajj7fk9nwcn35ivxy1lfda91f-hnix-runtime-0.1.0/bin/**)",
|
|
17
|
+
"Bash(/nix/store/f7xa531ajj7fk9nwcn35ivxy1lfda91f-hnix-runtime-0.1.0/bin/hnix-server --help)",
|
|
18
|
+
"Bash(uv sync:*)",
|
|
19
|
+
"Bash(mkdir -p /root/.config/nix)",
|
|
20
|
+
"Read(//root/.config/nix/**)",
|
|
21
|
+
"Bash(git add:*)",
|
|
22
|
+
"Bash(git commit:*)",
|
|
23
|
+
"Read(//usr/bin/**)",
|
|
24
|
+
"Bash(.venv/bin/pip list:*)",
|
|
25
|
+
"Bash(.venv/bin/python -c \"import fastapi; import hnix; print\\('ok'\\)\")",
|
|
26
|
+
"Bash(python3 -m venv .venv)",
|
|
27
|
+
"Bash(.venv/bin/pip install:*)",
|
|
28
|
+
"Bash(.venv/bin/python -c \"import fastapi; import hnix; print\\('all imports ok'\\)\")",
|
|
29
|
+
"Bash(.venv/bin/python -c \"from hnix.runtime.client import RuntimeClient; print\\('ok'\\)\")",
|
|
30
|
+
"Bash(.venv/bin/python -c \"from hnix.runtime.client import RuntimeClient; print\\('hnix import ok'\\)\")",
|
|
31
|
+
"Bash(git push:*)",
|
|
32
|
+
"Bash(gh repo:*)",
|
|
33
|
+
"Bash(git remote:*)",
|
|
34
|
+
"WebFetch(domain:www.harborframework.com)",
|
|
35
|
+
"Bash(/nix/store/0s7azxcgbkczrl5hjzvgi1g8qijlnqx2-claude-code-2.1.97/bin/claude:*)"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-integration
|
|
3
|
+
description: Use this skill when the user wants to add a new agent closure to Agentix-Agents-Hub, a new dataset/verifier closure to Agentix-Datasets, or port an existing integration from llm-agents.nix (numtide/llm-agents.nix) into Agentix's typed Python closure convention. Triggers include phrases like "add <name> to agents-hub", "port <name> from llm-agents.nix", "wrap the <cli> CLI as an Agentix closure", "integrate <tool> as a closure service", or any mention of building/adapting a new CLI-driven agent or evaluation harness to run inside an Agentix sandbox.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Agent Integration
|
|
8
|
+
|
|
9
|
+
Guide for packaging an external CLI-based agent (or a dataset/verifier) into an **Agentix typed Python closure** — a Docker image that ships a Python package the runtime imports in-process and exposes via `RuntimeClient.remote(<stub>, ...)` calls.
|
|
10
|
+
|
|
11
|
+
Upstream reference for Nix binary packaging:
|
|
12
|
+
|
|
13
|
+
- **llm-agents.nix** — how to pin versions and produce a hermetic `bin/<cli>`. Path: `../llm-agents.nix` (relative to the Agentix repo root).
|
|
14
|
+
|
|
15
|
+
CLI invocation logic (argv, env, flag flow) typically comes from the upstream agent's own docs or any existing wrapper you have handy; this skill describes how to land it in the Agentix closure shape.
|
|
16
|
+
|
|
17
|
+
## When to activate
|
|
18
|
+
|
|
19
|
+
- "add claude-code / aider / opencode / gemini-cli / qwen-code ... to agents-hub"
|
|
20
|
+
- "port <name> from llm-agents.nix" — llm-agents.nix is the source of truth for the derivation.
|
|
21
|
+
- "wrap the <X> CLI as an Agentix closure"
|
|
22
|
+
- "add a new dataset closure (e.g. humaneval, mbpp, livecodebench) to Agentix-Datasets"
|
|
23
|
+
- The user references llm-agents.nix as a reference / starting point for a new closure.
|
|
24
|
+
|
|
25
|
+
Do **not** use this skill for tasks that only touch `Agentix` core (runtime server, deployment, dispatcher). Those are core-substrate edits, not integrations.
|
|
26
|
+
|
|
27
|
+
## The Agentix closure ABI (non-negotiable)
|
|
28
|
+
|
|
29
|
+
The final image must satisfy:
|
|
30
|
+
|
|
31
|
+
- `VOLUME /nix`
|
|
32
|
+
- `/nix/store/<hash>-*/` — content-addressed Nix deps for everything the closure needs.
|
|
33
|
+
- `/nix/entry/python/agentix_closures/<name>/` — Python package the runtime imports.
|
|
34
|
+
- `__init__.py` — typed stubs (signatures only; body raises `NotImplementedError`).
|
|
35
|
+
- `_impl.py` — real implementations.
|
|
36
|
+
- `_register.py` — `def register() -> Dispatcher` that binds each stub to its impl.
|
|
37
|
+
- `/nix/entry/manifest.json` — `ClosureManifest` with `abi == AGENTIX_CLOSURE_ABI` and `package = "agentix_closures.<name>"`. This file is what marks `/mnt/<dir>` as a closure; without it the runtime ignores the mount.
|
|
38
|
+
- Optional: `/nix/entry/bin/<cli>` — native binaries the impl shells out to.
|
|
39
|
+
|
|
40
|
+
There is no `bin/start`, no UDS, no FastAPI app inside the closure. Caller-side typing flows from `RuntimeClient.remote(<stub>, ...)`, where `<stub>` is a regular Python callable imported from `agentix_closures.<name>`.
|
|
41
|
+
|
|
42
|
+
See `docs/closure-protocol.md` in Agentix for the full protocol.
|
|
43
|
+
|
|
44
|
+
## Each closure is self-contained
|
|
45
|
+
|
|
46
|
+
Every closure directory is an **independently maintained unit**. This is deliberately the opposite of llm-agents.nix's monorepo-with-blueprint organization.
|
|
47
|
+
|
|
48
|
+
- Own `Dockerfile`. No shared template file referenced across closures.
|
|
49
|
+
- Own `default.nix`. No `import ../<other>/` across closure boundaries, no shared `lib/` or `overlays/` at the repo root.
|
|
50
|
+
- Own `pyproject.toml`. The Python package is pip-installable on its own (`agentix-closure-<name>`).
|
|
51
|
+
- Own version pins. Two closures can run different versions of the same CLI — that's fine.
|
|
52
|
+
- Own lifecycle: build, test, bump, ship without touching siblings.
|
|
53
|
+
|
|
54
|
+
A single `default.nix` per closure is preferred (inline the binary derivation as a `let` binding); only split into a separate `package.nix` when the file gets unwieldy. Reference material in the Agentix core repo (e.g. `tests/closure-docker/Dockerfile`) is meant to be **copy-pasted as a starting point**, not referenced at build time.
|
|
55
|
+
|
|
56
|
+
## Target closure layout
|
|
57
|
+
|
|
58
|
+
Every migrated closure lives at `Agentix-Agents-Hub/<name>/` (agents) or `Agentix-Datasets/<name>/` (datasets) with this shape:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
<name>/
|
|
62
|
+
├── pyproject.toml # package metadata: name = "agentix-closure-<name>"
|
|
63
|
+
├── agentix_closures/
|
|
64
|
+
│ └── <name>/
|
|
65
|
+
│ ├── __init__.py # typed stubs
|
|
66
|
+
│ ├── _impl.py # real implementation
|
|
67
|
+
│ └── _register.py # register() -> Dispatcher
|
|
68
|
+
├── manifest.json # ClosureManifest (abi, package, ...)
|
|
69
|
+
├── default.nix # binary derivation(s) + buildPythonPackage + symlinkJoin
|
|
70
|
+
└── Dockerfile # closure image: nix-build → /export → final layer
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`__init__.py`, `_impl.py`, `_register.py`, `manifest.json`, `default.nix`, `Dockerfile`, `pyproject.toml` are all mandatory.
|
|
74
|
+
|
|
75
|
+
## Source mapping
|
|
76
|
+
|
|
77
|
+
**The governing principle: locate → extract, don't copy.** Upstream packaging carries a lot of machinery we don't use (Blueprint flakes, overlays, custom lib hooks, update scripts). For each new closure you only need a few facts out of each source file — everything else goes straight in the bin.
|
|
78
|
+
|
|
79
|
+
### Lift the CLI invocation into `_impl.py`
|
|
80
|
+
|
|
81
|
+
Whatever your reference is (the upstream CLI's README, an existing harness, your own notes), boil it down to three things and forget the rest:
|
|
82
|
+
|
|
83
|
+
- **argv** — the exact subprocess argument list (e.g. `claude -p <prompt> --output-format=stream-json --permission-mode=bypassPermissions --print --`). Drop it literally into `_impl.py`'s `subprocess` call.
|
|
84
|
+
- **fixed env** — variables the agent hardcodes regardless of caller (`DISABLE_AUTOUPDATER=1`, `IS_SANDBOX=1`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, …). Set these inside `_impl.run` when building the subprocess env.
|
|
85
|
+
- **caller-provided env** — variables the agent reads at runtime (`ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, model selection, …). Expose as a typed `env: dict[str, str] | None = None` keyword argument on the stub; `_impl.run` layers it over `os.environ`.
|
|
86
|
+
|
|
87
|
+
Ignore everything else — Pydantic config schemas, trajectory parsing, multi-backend abstractions, `apt-get`/`npm install` setup logic (Nix replaces them).
|
|
88
|
+
|
|
89
|
+
**Skills / memory / MCP registration**: when the agent normally pulls these from host-side paths (`~/.claude/skills`, etc.), expose them as **typed dict parameters on the stub**, not filesystem paths. A closure has no "host" — the orchestrator ships content inline. Pattern:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
def run(
|
|
93
|
+
instruction: str,
|
|
94
|
+
*,
|
|
95
|
+
skills: dict[str, str] | None = None, # {relpath: content}
|
|
96
|
+
memory: dict[str, str] | None = None,
|
|
97
|
+
mcp_servers: list[dict[str, Any]] | None = None,
|
|
98
|
+
) -> RunResult: ...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`_impl.py` materialises these under `$CLAUDE_CONFIG_DIR` (or the agent's config dir) right before invoking the CLI. Reject keys that are absolute or contain `..`. Skip the whole mechanism if your agent doesn't use any of it.
|
|
102
|
+
|
|
103
|
+
### From llm-agents.nix — lift the source descriptor
|
|
104
|
+
|
|
105
|
+
Open `llm-agents.nix/packages/<name>/{package.nix,hashes.json}` and lift just these:
|
|
106
|
+
|
|
107
|
+
- **source descriptor** (URL template + platform map, `fetchFromGitHub` args, or npm tarball URL).
|
|
108
|
+
- **version + hash values** (merge `hashes.json` contents into the derivation — no side-car).
|
|
109
|
+
- **wrap env / PATH additions** from `postFixup` / `postInstall` (only those the agent actually needs).
|
|
110
|
+
|
|
111
|
+
Ignore the rest: Blueprint flake auto-discovery, `overlays/default.nix`, custom hooks (`wrapBuddy`, `versionCheckHook`, `versionCheckHomeHook`, `fetchNpmDepsWithPackuments` — drop these or substitute plain nixpkgs equivalents), `__noChroot`, `passthru.category`, `update.py`.
|
|
112
|
+
|
|
113
|
+
Three derivation shapes cover ~95% of cases. Use `finalAttrs` so the version appears once.
|
|
114
|
+
|
|
115
|
+
**Binary blob** (claude-code, gemini-cli, …):
|
|
116
|
+
```nix
|
|
117
|
+
stdenv.mkDerivation (finalAttrs: {
|
|
118
|
+
pname = "<name>"; version = "2.1.114";
|
|
119
|
+
src = fetchurl {
|
|
120
|
+
url = "https://<cdn>/${finalAttrs.version}/linux-x64/<binary>";
|
|
121
|
+
hash = "sha256-...";
|
|
122
|
+
};
|
|
123
|
+
dontUnpack = true;
|
|
124
|
+
nativeBuildInputs = [ makeWrapper ];
|
|
125
|
+
installPhase = ''install -Dm755 $src $out/bin/<binary>'';
|
|
126
|
+
postFixup = ''wrapProgram $out/bin/<binary> --set DISABLE_AUTOUPDATER 1'';
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**npm package** (pi, opencode, …):
|
|
131
|
+
```nix
|
|
132
|
+
buildNpmPackage (finalAttrs: {
|
|
133
|
+
pname = "<name>"; version = "0.67.68";
|
|
134
|
+
src = fetchurl {
|
|
135
|
+
url = "https://registry.npmjs.org/<pkg>/-/<pkg>-${finalAttrs.version}.tgz";
|
|
136
|
+
hash = "sha256-...";
|
|
137
|
+
};
|
|
138
|
+
npmDepsHash = "sha256-...";
|
|
139
|
+
dontNpmBuild = true;
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Rust from source** (code, agent-browser, …):
|
|
144
|
+
```nix
|
|
145
|
+
rustPlatform.buildRustPackage (finalAttrs: {
|
|
146
|
+
pname = "<name>"; version = "0.6.93";
|
|
147
|
+
src = fetchFromGitHub {
|
|
148
|
+
owner = "..."; repo = "..."; tag = "v${finalAttrs.version}";
|
|
149
|
+
hash = "sha256-...";
|
|
150
|
+
};
|
|
151
|
+
cargoHash = "sha256-...";
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Migration playbook
|
|
156
|
+
|
|
157
|
+
### 1. Locate source files
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
llm-agents.nix/packages/<name>/{package.nix,hashes.json}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Plus whatever reference describes the CLI's invocation (the agent's README, an existing wrapper, etc.). Read first, then lift 2-3 specific pieces (see "source mapping" above) and stitch them together — don't copy verbatim.
|
|
164
|
+
|
|
165
|
+
### 2. `__init__.py` — typed stubs
|
|
166
|
+
|
|
167
|
+
Define the request/response dataclasses and the function signatures. Function bodies raise `NotImplementedError` — they're never called on the caller side.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from __future__ import annotations
|
|
171
|
+
from dataclasses import dataclass
|
|
172
|
+
from typing import Literal
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class RunResult:
|
|
176
|
+
exit_code: int
|
|
177
|
+
stdout: str
|
|
178
|
+
stderr: str
|
|
179
|
+
patch: str
|
|
180
|
+
|
|
181
|
+
def run(
|
|
182
|
+
instruction: str,
|
|
183
|
+
*,
|
|
184
|
+
workdir: str = "/testbed",
|
|
185
|
+
timeout: float = 600,
|
|
186
|
+
model: str | None = None,
|
|
187
|
+
env: dict[str, str] | None = None,
|
|
188
|
+
# … other agent-specific knobs
|
|
189
|
+
) -> RunResult:
|
|
190
|
+
"""Doc for callers — what the agent does, what each arg means."""
|
|
191
|
+
raise NotImplementedError("call via RuntimeClient.remote(<name>.run, ...)")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The signature is the contract. mypy / pyright validate the caller; pydantic on the wire validates the over-the-wire payload.
|
|
195
|
+
|
|
196
|
+
### 3. `_impl.py` — real bodies
|
|
197
|
+
|
|
198
|
+
Same signature, real body. Subprocess the CLI, build env, capture output, return `RunResult`.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
import asyncio, os
|
|
202
|
+
from . import RunResult
|
|
203
|
+
|
|
204
|
+
async def run(instruction: str, *, workdir: str = "/testbed", ...) -> RunResult:
|
|
205
|
+
env = _build_env(...)
|
|
206
|
+
argv = ["<cli>", "--foo", "bar", "-p", instruction]
|
|
207
|
+
proc = await asyncio.create_subprocess_exec(
|
|
208
|
+
*argv, stdout=PIPE, stderr=PIPE, cwd=workdir, env=env,
|
|
209
|
+
)
|
|
210
|
+
out_b, err_b = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
|
211
|
+
return RunResult(
|
|
212
|
+
exit_code=proc.returncode or 0,
|
|
213
|
+
stdout=out_b.decode(),
|
|
214
|
+
stderr=err_b.decode(),
|
|
215
|
+
patch=await _collect_patch(workdir),
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`_impl.py` can be sync or async — the dispatcher awaits awaitable returns.
|
|
220
|
+
|
|
221
|
+
### 4. `_register.py` — bind stubs to impls
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from agentix.dispatch import Dispatcher
|
|
225
|
+
from . import run
|
|
226
|
+
from ._impl import run as _run_impl
|
|
227
|
+
|
|
228
|
+
def register() -> Dispatcher:
|
|
229
|
+
d = Dispatcher()
|
|
230
|
+
d.bind(run, _run_impl)
|
|
231
|
+
return d
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Pure function, no globals. Runtime calls it once on startup.
|
|
235
|
+
|
|
236
|
+
### 5. `manifest.json`
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"abi": 1,
|
|
241
|
+
"name": "<name>",
|
|
242
|
+
"version": "0.1.0",
|
|
243
|
+
"kind": "agent",
|
|
244
|
+
"package": "agentix_closures.<name>",
|
|
245
|
+
"description": "Short blurb"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 6. `pyproject.toml`
|
|
250
|
+
|
|
251
|
+
```toml
|
|
252
|
+
[build-system]
|
|
253
|
+
requires = ["hatchling"]
|
|
254
|
+
build-backend = "hatchling.build"
|
|
255
|
+
|
|
256
|
+
[project]
|
|
257
|
+
name = "agentix-closure-<name>"
|
|
258
|
+
version = "0.1.0"
|
|
259
|
+
requires-python = ">=3.11"
|
|
260
|
+
dependencies = [] # closures share the runtime's interpreter; keep this empty
|
|
261
|
+
|
|
262
|
+
[tool.hatch.build.targets.wheel]
|
|
263
|
+
packages = ["agentix_closures/<name>"]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 7. `default.nix`
|
|
267
|
+
|
|
268
|
+
Composition: binary derivation + `buildPythonPackage` for the Python wheel + `symlinkJoin` to assemble the final tree.
|
|
269
|
+
|
|
270
|
+
```nix
|
|
271
|
+
{ pkgs ? import <nixpkgs> {} }:
|
|
272
|
+
let
|
|
273
|
+
binary = pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
274
|
+
pname = "<name>"; version = "X.Y.Z";
|
|
275
|
+
src = pkgs.fetchurl { url = "..."; hash = "sha256-..."; };
|
|
276
|
+
# ... installPhase, postFixup, etc.
|
|
277
|
+
});
|
|
278
|
+
python = pkgs.python312;
|
|
279
|
+
closurePkg = python.pkgs.buildPythonPackage {
|
|
280
|
+
pname = "agentix-closure-<name>"; version = "0.1.0"; format = "pyproject";
|
|
281
|
+
src = ./.;
|
|
282
|
+
nativeBuildInputs = [ python.pkgs.hatchling ];
|
|
283
|
+
doCheck = false;
|
|
284
|
+
};
|
|
285
|
+
in
|
|
286
|
+
pkgs.symlinkJoin {
|
|
287
|
+
name = "agentix-closure-<name>";
|
|
288
|
+
paths = [
|
|
289
|
+
binary
|
|
290
|
+
(pkgs.runCommand "<name>-python" { } ''
|
|
291
|
+
mkdir -p $out/python
|
|
292
|
+
cp -r ${closurePkg}/lib/python${python.pythonVersion}/site-packages/agentix_closures $out/python/
|
|
293
|
+
cp ${./manifest.json} $out/manifest.json
|
|
294
|
+
'')
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 8. `Dockerfile`
|
|
300
|
+
|
|
301
|
+
Copy `Agentix/tests/closure-docker/Dockerfile` into the closure dir as a starting point. **Context = the closure dir itself** (`<name>/`) — closures are self-contained.
|
|
302
|
+
|
|
303
|
+
If the closure needs a flake input (e.g. `numtide/llm-agents.nix` for the `claude` binary), put a local `flake.nix` + `flake.lock` *inside the closure dir* and build with `nix build .#<name>` inside the Dockerfile's builder stage.
|
|
304
|
+
|
|
305
|
+
Labels worth adding: `LABEL org.agentix.closure=1`, `LABEL org.agentix.closure.kind=agent` (or `dataset`), `LABEL org.agentix.closure.name=<name>`.
|
|
306
|
+
|
|
307
|
+
### 9. No registration step
|
|
308
|
+
|
|
309
|
+
There's no repo-level registry to update. Each closure's identity is its built Docker image ref plus its `manifest.package`. Callers reference the image by tag in `SandboxConfig(closures=[...])` and import the typed stub: `from agentix_closures import <name>`.
|
|
310
|
+
|
|
311
|
+
### 10. Build and smoke-test
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
docker build -t agentix-hub/<name>:0.1.0 <name>/ # context = the closure dir
|
|
315
|
+
|
|
316
|
+
# In a tiny script:
|
|
317
|
+
import asyncio
|
|
318
|
+
from agentix import RuntimeClient, SandboxConfig
|
|
319
|
+
from agentix.deployment.docker import DockerDeployment
|
|
320
|
+
from agentix_closures import <name>
|
|
321
|
+
|
|
322
|
+
async def check():
|
|
323
|
+
cfg = SandboxConfig(
|
|
324
|
+
image="ubuntu:24.04",
|
|
325
|
+
runtime="agentix/runtime:0.1.0",
|
|
326
|
+
closures=["agentix-hub/<name>:0.1.0"],
|
|
327
|
+
)
|
|
328
|
+
async with DockerDeployment().session(cfg) as sb:
|
|
329
|
+
async with RuntimeClient(sb.runtime_url) as c:
|
|
330
|
+
print(await c.closures())
|
|
331
|
+
result = await c.remote(<name>.run, instruction="...")
|
|
332
|
+
print(result)
|
|
333
|
+
asyncio.run(check())
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Verify the typed return shape, exit code, stdout, patch. Then add an end-to-end test against a known task (e.g. a SWE-bench instance) before declaring done.
|
|
337
|
+
|
|
338
|
+
## Common pitfalls
|
|
339
|
+
|
|
340
|
+
- **Body in the stub** — `__init__.py` functions must `raise NotImplementedError`, not contain real logic. Real bodies belong in `_impl.py`. A stub with a working body will silently run on the caller side instead of being dispatched.
|
|
341
|
+
- **Imported deps in `__init__.py`** — keep stub imports minimal (stdlib + dataclasses + typing). Heavy imports (subprocess wrappers, fastapi, etc.) belong in `_impl.py` so caller-side `from agentix_closures import <name>` stays cheap and dependency-free.
|
|
342
|
+
- **Agent binary not on PATH inside impl** — your derivation's `symlinkJoin` must produce `bin/<cli>` so it lands at `/mnt/<dir>/entry/bin/<cli>`. `nix-build && ls result/bin/` to verify. The impl invokes the CLI by name; the runtime sandbox makes `entry/bin` resolvable via `paths_from`.
|
|
343
|
+
- **Trajectory overload** — resist piping the agent's full step-by-step trajectory through `run()`. Return `{exit_code, stdout, stderr, patch}` and let callers opt in to richer formats via a separate stub method.
|
|
344
|
+
- **System deps at runtime** — don't add `apt-get install git` in the Dockerfile runtime stage. Put `pkgs.git` in the derivation. The task image usually has `git` already.
|
|
345
|
+
- **Closure Python deps** — keep `pyproject.toml`'s `dependencies = []`. Closures share the runtime's interpreter (`pydantic` is already there). Adding a third-party Python dep to a closure adds risk of collision with other closures' deps.
|
|
346
|
+
- **Flake lives in the closure dir** — if the closure needs a flake input, put the `flake.nix` / `flake.lock` in that closure's own directory and build with `nix build .#<name>` inside the Dockerfile.
|
|
347
|
+
- **Version bumps** — llm-agents.nix uses `hashes.json` + `update.py` for upgrades. We inline everything; bumping a version is a manual edit of the `version = ...` and matching hash lines.
|
|
348
|
+
|
|
349
|
+
## Datasets variation
|
|
350
|
+
|
|
351
|
+
For `Agentix-Datasets/<name>/`, the layout is the same but the stubs export `setup` and `verify` instead of `run`:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
@dataclass
|
|
355
|
+
class SetupResult:
|
|
356
|
+
instruction: str
|
|
357
|
+
workdir: str
|
|
358
|
+
instance_id: str
|
|
359
|
+
|
|
360
|
+
@dataclass
|
|
361
|
+
class VerifyResult:
|
|
362
|
+
passed: bool
|
|
363
|
+
reason: str
|
|
364
|
+
|
|
365
|
+
def setup(instance_id: str) -> SetupResult: ...
|
|
366
|
+
def verify(patch: str, instance_id: str) -> VerifyResult: ...
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
There's rarely an external binary to vendor — most datasets only need Python + test tooling. `default.nix` depends on `<nixpkgs>` directly.
|
|
370
|
+
|
|
371
|
+
See `Agentix-Datasets/swebench/` for a working reference.
|
|
372
|
+
|
|
373
|
+
## Worked example pointer
|
|
374
|
+
|
|
375
|
+
- llm-agents.nix source: `llm-agents.nix/packages/claude-code/{package.nix,hashes.json}`
|
|
376
|
+
- Agentix target: `Agentix-Agents-Hub/claude-code/`
|
|
377
|
+
|
|
378
|
+
When migrating a *new* agent, diff your work against claude-code's target — it's the canonical reference for the typed-Python-closure shape.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- ".github/workflows/docs.yml"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: docs-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build-and-deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: "20"
|
|
24
|
+
|
|
25
|
+
- name: Install Mintlify CLI
|
|
26
|
+
run: npm install -g mint
|
|
27
|
+
|
|
28
|
+
- name: Validate docs (strict)
|
|
29
|
+
working-directory: docs
|
|
30
|
+
run: mint validate
|
|
31
|
+
|
|
32
|
+
- name: Build static export
|
|
33
|
+
working-directory: docs
|
|
34
|
+
run: |
|
|
35
|
+
mint export
|
|
36
|
+
rm -rf _site
|
|
37
|
+
mkdir _site
|
|
38
|
+
unzip -q export.zip -d _site
|
|
39
|
+
touch _site/.nojekyll
|
|
40
|
+
|
|
41
|
+
- name: Match code-block font size to body text
|
|
42
|
+
# docs.json has no `customCss` and `fonts` has no `code` sub-object
|
|
43
|
+
# (verified against settings-appearance docs). Inject a tiny <style>
|
|
44
|
+
# block into every page's <head> so fenced code renders at the same
|
|
45
|
+
# size as surrounding prose instead of Mintlify's larger default.
|
|
46
|
+
working-directory: docs
|
|
47
|
+
run: |
|
|
48
|
+
python3 - <<'PY'
|
|
49
|
+
import pathlib
|
|
50
|
+
snippet = (
|
|
51
|
+
'<style>pre,pre *{font-size:1em!important;'
|
|
52
|
+
'line-height:1.6!important}</style>'
|
|
53
|
+
)
|
|
54
|
+
for html in pathlib.Path('_site').rglob('*.html'):
|
|
55
|
+
s = html.read_text()
|
|
56
|
+
if '</head>' in s and snippet not in s:
|
|
57
|
+
html.write_text(s.replace('</head>', snippet + '</head>', 1))
|
|
58
|
+
PY
|
|
59
|
+
|
|
60
|
+
- name: Deploy to Agentiix.github.io
|
|
61
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
62
|
+
with:
|
|
63
|
+
personal_token: ${{ secrets.DOCS_DEPLOY_TOKEN }}
|
|
64
|
+
external_repository: Agentiix/Agentiix.github.io
|
|
65
|
+
publish_branch: main
|
|
66
|
+
publish_dir: docs/_site
|
|
67
|
+
force_orphan: true
|
|
68
|
+
commit_message: "docs: deploy ${{ github.sha }}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: test-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
unit:
|
|
14
|
+
name: unit + lint
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
cache: pip
|
|
22
|
+
- run: pip install -e '.[dev]'
|
|
23
|
+
- name: lint
|
|
24
|
+
run: ruff check agentix/ tests/
|
|
25
|
+
- name: types
|
|
26
|
+
run: pyright agentix
|
|
27
|
+
- name: unit tests
|
|
28
|
+
run: pytest tests/ -v
|