agent-apprenticeship 0.1.1 → 0.1.2
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.
- package/README.md +6 -15
- package/bin/agent-apprenticeship.js +56 -12
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/agent_apprenticeship_trace/__init__.py +1 -1
- package/src/agent_apprenticeship_trace/apprentice_adapters.py +2 -1
- package/src/agent_apprenticeship_trace/bundle_exporter.py +63 -3
- package/src/agent_apprenticeship_trace/cli.py +366 -53
- package/src/agent_apprenticeship_trace/codex_runner.py +46 -3
- package/src/agent_apprenticeship_trace/config.py +16 -0
- package/src/agent_apprenticeship_trace/openai_structured.py +6 -0
- package/src/agent_apprenticeship_trace/public_run.py +118 -57
- package/src/agent_apprenticeship_trace/task_intake.py +45 -2
package/README.md
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
# Agent Apprenticeship
|
|
2
2
|
|
|
3
|
-
The living ecosystem where AI agents learn from real-world work through iterative loops, reusable experience, and training
|
|
3
|
+
The living ecosystem where AI agents learn from real-world work through iterative workflow loops, reusable experience, and collective training signal exchange.
|
|
4
4
|
|
|
5
|
-
As agents move into long-horizon, economically valuable work, Agent Apprenticeship creates the open infrastructure where
|
|
5
|
+
As agents move into long-horizon, economically valuable work, Agent Apprenticeship creates the open infrastructure where real-world tasks generate reusable learning signals and complex workflows advance through agent loops that turn execution into shared improvement.
|
|
6
6
|
|
|
7
|
-
Agent Apprenticeship is designed for
|
|
7
|
+
Agent Apprenticeship is designed for a compounding exchange of agent work experience: economically valuable task execution generates training signals, those signals improve future work, and future work creates new reusable experience for the ecosystem.
|
|
8
8
|
|
|
9
|
-
Agent Apprenticeship is built for
|
|
9
|
+
Agent Apprenticeship is built for iterative workflow loops across domains, from simple tasks to complex specialized work. Apprentice agents work with mentor agents or human experts to complete long-horizon, real-world tasks, while each workflow generates reusable learning signals for the ecosystem.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Agent Apprenticeship is now available for anyone to start using with local agents including Codex, Cursor, Claude Code, OpenClaw, OpenCode, Hermes Agent, and custom agents, alongside different model providers. Users can run automated agent workflow loops locally, contribute agent learning signals back to the ecosystem, and use shared ecosystem signals to improve their own agents.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
* 495 reusable agent lessons
|
|
15
|
-
* 1000+ full agent execution traces
|
|
16
|
-
* 1000+ agent work episodes / task rollouts
|
|
17
|
-
|
|
18
|
-
The seed dataset spans specialized economically valuable tasks across domains and forms the first layer of the Agent Apprenticeship ecosystem.
|
|
19
|
-
|
|
20
|
-
Agent Apprenticeship is now available for anyone to start using with local agents including Codex, Cursor, Claude Code, OpenClaw, OpenCode, Hermes Agent, and custom agents, alongside different model providers. Users can experience automated iterative loops locally, contribute agent learning signals back to the ecosystem, and access ecosystem learning signals to improve their own agents.
|
|
21
|
-
|
|
22
|
-
Agent Apprenticeship is also about the future of work and the economic value of agents. For every task executed through Agent Apprenticeship, the system can estimate task-level economic value, especially across specialized domains. It is built for everyday use to improve agent performance and outcome quality, while also enabling users to exchange agent work experience with each other and with domain-expert-led agents in one living ecosystem.
|
|
13
|
+
Agent Apprenticeship is about the future of work and the economic value of agents. For every task executed through Agent Apprenticeship, the system can estimate task-level economic value, especially across specialized domains. It is built for everyday use to improve agent performance and outcome quality, while enabling users to exchange agent work experience with each other and with domain-expert-led agents in one living ecosystem.
|
|
23
14
|
|
|
24
15
|
## Install
|
|
25
16
|
|
|
@@ -64,15 +64,15 @@ function findPython() {
|
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
66
|
const candidates = [
|
|
67
|
-
"python3.13",
|
|
68
|
-
"python3.12",
|
|
69
67
|
"python3.11",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
68
|
+
"python3.12",
|
|
69
|
+
"python3.13",
|
|
72
70
|
"/opt/homebrew/bin/python3.11",
|
|
73
|
-
"/
|
|
74
|
-
"/
|
|
71
|
+
"/opt/homebrew/bin/python3.12",
|
|
72
|
+
"/opt/homebrew/bin/python3.13",
|
|
75
73
|
"/usr/local/bin/python3.11",
|
|
74
|
+
"/usr/local/bin/python3.12",
|
|
75
|
+
"/usr/local/bin/python3.13",
|
|
76
76
|
"python3",
|
|
77
77
|
"python"
|
|
78
78
|
];
|
|
@@ -88,17 +88,51 @@ function venvPython(venvDir) {
|
|
|
88
88
|
: path.join(venvDir, "bin", "python");
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function
|
|
91
|
+
function runtimeEnv() {
|
|
92
|
+
const env = { ...process.env };
|
|
93
|
+
if (!env.PIP_CACHE_DIR) {
|
|
94
|
+
env.PIP_CACHE_DIR = path.join(appHome(), "pip-cache");
|
|
95
|
+
}
|
|
96
|
+
return env;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setupTimeout(defaultMs) {
|
|
100
|
+
const raw = process.env.AA_NPM_SETUP_TIMEOUT_MS;
|
|
101
|
+
if (!raw) return defaultMs;
|
|
102
|
+
const value = Number(raw);
|
|
103
|
+
return Number.isFinite(value) && value > 0 ? value : defaultMs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function runQuiet(command, args, label, timeoutMs) {
|
|
92
107
|
const result = spawnSync(command, args, {
|
|
93
108
|
encoding: "utf8",
|
|
94
109
|
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
-
env:
|
|
110
|
+
env: runtimeEnv(),
|
|
111
|
+
timeout: setupTimeout(timeoutMs)
|
|
96
112
|
});
|
|
97
113
|
if (result.error || result.status !== 0) {
|
|
98
114
|
console.error(`Agent Apprenticeship runtime setup failed while trying to ${label}.`);
|
|
99
|
-
|
|
115
|
+
const timedOut = Boolean(result.signal) || (result.error && String(result.error.message || result.error).includes("ETIMEDOUT"));
|
|
116
|
+
if (timedOut) {
|
|
117
|
+
console.error("Runtime setup timed out. Re-run the command, or set AA_PYTHON to a working Python 3.11+ interpreter.");
|
|
118
|
+
} else if (result.error) {
|
|
119
|
+
const message = String(result.error.message || result.error);
|
|
120
|
+
console.error(message);
|
|
121
|
+
}
|
|
100
122
|
const out = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
101
|
-
if (out) console.error(out);
|
|
123
|
+
if (out) console.error(out.slice(-4000));
|
|
124
|
+
process.exit(result.status || 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function runVisible(command, args, label) {
|
|
129
|
+
const result = spawnSync(command, args, {
|
|
130
|
+
stdio: "inherit",
|
|
131
|
+
env: runtimeEnv()
|
|
132
|
+
});
|
|
133
|
+
if (result.error || result.status !== 0) {
|
|
134
|
+
console.error(`Agent Apprenticeship runtime setup failed while trying to ${label}.`);
|
|
135
|
+
if (result.error) console.error(String(result.error.message || result.error));
|
|
102
136
|
process.exit(result.status || 1);
|
|
103
137
|
}
|
|
104
138
|
}
|
|
@@ -117,10 +151,20 @@ function ensureRuntime(python) {
|
|
|
117
151
|
}
|
|
118
152
|
|
|
119
153
|
console.error("Installing Agent Apprenticeship runtime...");
|
|
154
|
+
console.error("First run can take a few minutes while Python dependencies are installed.");
|
|
120
155
|
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
121
156
|
fs.mkdirSync(path.dirname(venvDir), { recursive: true });
|
|
122
|
-
runQuiet(python, ["-m", "venv", venvDir], "create the Python environment");
|
|
123
|
-
runQuiet(py, [
|
|
157
|
+
runQuiet(python, ["-m", "venv", venvDir], "create the Python environment", 120000);
|
|
158
|
+
runQuiet(py, [
|
|
159
|
+
"-m", "pip", "install",
|
|
160
|
+
"-q",
|
|
161
|
+
"--disable-pip-version-check",
|
|
162
|
+
"--progress-bar", "off",
|
|
163
|
+
"--no-input",
|
|
164
|
+
"--retries", "10",
|
|
165
|
+
"--timeout", "60",
|
|
166
|
+
packageRoot
|
|
167
|
+
], "install the Python package", 600000);
|
|
124
168
|
fs.writeFileSync(markerPath, JSON.stringify({
|
|
125
169
|
packageName: packageJson.name,
|
|
126
170
|
packageVersion: packageJson.version,
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-apprenticeship"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Open framework for turning real agent work into transferable agent experience"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Agent Apprenticeship framework."""
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.2"
|
|
@@ -14,6 +14,7 @@ from .codex_runner import (
|
|
|
14
14
|
_attempt_dir,
|
|
15
15
|
_copy_attempt_inputs,
|
|
16
16
|
_deliverables,
|
|
17
|
+
_run_with_process_group_timeout,
|
|
17
18
|
ensure_attempt_outputs,
|
|
18
19
|
)
|
|
19
20
|
from .config import get_settings
|
|
@@ -271,7 +272,7 @@ def run_external_agent_attempt(package_root: Path, raw: RawTaskRecord, spec: Tas
|
|
|
271
272
|
run_error = RuntimeError(invocation.unsupported_reason)
|
|
272
273
|
else:
|
|
273
274
|
try:
|
|
274
|
-
cp =
|
|
275
|
+
cp = _run_with_process_group_timeout(invocation.argv, cwd=d, timeout=timeout or settings.task_timeout_seconds)
|
|
275
276
|
returncode = cp.returncode
|
|
276
277
|
stdout = cp.stdout or ""
|
|
277
278
|
stderr = cp.stderr or ""
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from .config import Settings, get_settings, normalize_mentor_mode
|
|
10
10
|
from .io import append_jsonl, read_json, read_jsonl, write_json
|
|
11
|
-
from .public_sanitizer import sanitize_public_obj
|
|
11
|
+
from .public_sanitizer import sanitize_public_obj, sanitize_public_text
|
|
12
12
|
from .release_exporter import create_release
|
|
13
13
|
|
|
14
14
|
|
|
@@ -50,6 +50,30 @@ def _copy_json_if_exists(src: Path, dst: Path) -> bool:
|
|
|
50
50
|
return True
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def _copy_public_file(src: Path, dst: Path) -> bool:
|
|
54
|
+
if not src.exists() or not src.is_file():
|
|
55
|
+
return False
|
|
56
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
if src.suffix == ".json":
|
|
58
|
+
try:
|
|
59
|
+
write_json(dst, _sanitize_bundle_json(read_json(src)))
|
|
60
|
+
except Exception:
|
|
61
|
+
dst.write_text(sanitize_public_text(src.read_text(errors="replace")) or "")
|
|
62
|
+
elif src.suffix == ".jsonl":
|
|
63
|
+
try:
|
|
64
|
+
dst.write_text("")
|
|
65
|
+
for row in read_jsonl(src):
|
|
66
|
+
append_jsonl(dst, sanitize_public_obj(row) if isinstance(row, dict) else row)
|
|
67
|
+
except Exception:
|
|
68
|
+
dst.write_text(sanitize_public_text(src.read_text(errors="replace")) or "")
|
|
69
|
+
else:
|
|
70
|
+
try:
|
|
71
|
+
dst.write_text(sanitize_public_text(src.read_text(errors="replace")) or "")
|
|
72
|
+
except UnicodeDecodeError:
|
|
73
|
+
shutil.copy2(src, dst)
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
53
77
|
def _run_status(release_root: Path) -> str:
|
|
54
78
|
rows = read_jsonl(release_root / "packages_index.jsonl")
|
|
55
79
|
statuses = [row.get("task_status") for row in rows if row.get("task_status")]
|
|
@@ -73,10 +97,10 @@ def contribution_counts(release_root: Path) -> dict[str, int]:
|
|
|
73
97
|
def _write_card(bundle_root: Path) -> None:
|
|
74
98
|
(bundle_root / "contribution_card.md").write_text(
|
|
75
99
|
"# Agent Apprenticeship Contribution Bundle\n\n"
|
|
76
|
-
"This local bundle packages an apprenticeship session for
|
|
100
|
+
"This local bundle packages an apprenticeship session for public ecosystem contribution. "
|
|
77
101
|
"It includes session events, task instructions, traces, actual outputs, evaluation results, "
|
|
78
102
|
"and learning-data views when available.\n\n"
|
|
79
|
-
"No automatic upload is performed.
|
|
103
|
+
"No automatic upload is performed. Inspect this folder locally before sharing.\n\n"
|
|
80
104
|
"To contribute, submit the bundle to the Agent Apprenticeship ecosystem repo, "
|
|
81
105
|
"or get help in Slack: https://join.slack.com/t/fsycommunity/shared_invite/zt-37417grrb-jFD6BQIYgC5wEMrW2bHssw\n"
|
|
82
106
|
)
|
|
@@ -128,6 +152,7 @@ def _copy_clean_bundle_files(release_root: Path, bundle_root: Path, include_debu
|
|
|
128
152
|
|
|
129
153
|
_copy_jsonl_if_nonempty(release_root / "actual_outputs.jsonl", bundle_root / "outputs" / "actual_outputs.jsonl")
|
|
130
154
|
_copy_json_if_exists(release_root / "artifacts_index.json", bundle_root / "outputs" / "artifacts_index.json")
|
|
155
|
+
_copy_indexed_package_files(release_root, bundle_root)
|
|
131
156
|
|
|
132
157
|
for name in [
|
|
133
158
|
"grader_results.jsonl",
|
|
@@ -159,6 +184,41 @@ def _copy_clean_bundle_files(release_root: Path, bundle_root: Path, include_debu
|
|
|
159
184
|
shutil.rmtree(bundle_root / "debug")
|
|
160
185
|
|
|
161
186
|
|
|
187
|
+
def _copy_indexed_package_files(release_root: Path, bundle_root: Path) -> None:
|
|
188
|
+
index_path = release_root / "artifacts_index.json"
|
|
189
|
+
if not index_path.exists():
|
|
190
|
+
return
|
|
191
|
+
raw_rows = read_json(index_path)
|
|
192
|
+
if not isinstance(raw_rows, list):
|
|
193
|
+
return
|
|
194
|
+
task_ids = sorted({str(row.get("task_id")) for row in raw_rows if isinstance(row, dict) and row.get("task_id")})
|
|
195
|
+
multiple_tasks = len(task_ids) > 1
|
|
196
|
+
rewritten: list[dict[str, Any]] = []
|
|
197
|
+
for row in raw_rows:
|
|
198
|
+
if not isinstance(row, dict):
|
|
199
|
+
continue
|
|
200
|
+
rel = str(row.get("package_relative_path") or "")
|
|
201
|
+
task_id = str(row.get("task_id") or "")
|
|
202
|
+
if not rel or not task_id or is_unsafe_bundle_ref(rel):
|
|
203
|
+
rewritten.append(sanitize_public_obj(row))
|
|
204
|
+
continue
|
|
205
|
+
src = release_root / "packages" / task_id / rel
|
|
206
|
+
bundle_rel = f"packages/{task_id}/{rel}" if multiple_tasks else rel
|
|
207
|
+
clean_row = sanitize_public_obj(dict(row))
|
|
208
|
+
clean_row["package_relative_path"] = bundle_rel
|
|
209
|
+
if src.exists() and _copy_public_file(src, bundle_root / bundle_rel):
|
|
210
|
+
clean_row.pop("artifact_missing", None)
|
|
211
|
+
else:
|
|
212
|
+
clean_row["artifact_missing"] = True
|
|
213
|
+
rewritten.append(clean_row)
|
|
214
|
+
write_json(bundle_root / "outputs" / "artifacts_index.json", rewritten)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_unsafe_bundle_ref(ref: str) -> bool:
|
|
218
|
+
path = Path(ref)
|
|
219
|
+
return path.is_absolute() or ".." in path.parts
|
|
220
|
+
|
|
221
|
+
|
|
162
222
|
def _first_jsonl_row(path: Path) -> dict[str, Any]:
|
|
163
223
|
rows = read_jsonl(path) if path.exists() else []
|
|
164
224
|
for row in rows:
|