agent-apprenticeship 0.2.0 → 0.3.0
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 +269 -3
- package/bin/agent-apprenticeship.js +1 -2
- package/package.json +4 -4
- package/pyproject.toml +10 -4
- package/schemas/environment_replay.schema.json +41 -0
- package/schemas/eval_package.schema.json +37 -0
- package/schemas/experience_compilation.schema.json +53 -0
- package/schemas/runtime_training.schema.json +33 -0
- package/schemas/source_evidence_map.schema.json +25 -0
- package/schemas/trace.schema.json +904 -0
- package/schemas/trace_step.schema.json +622 -0
- package/schemas/training_rows.schema.json +29 -0
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/__init__.py +1 -1
- package/src/agent_apprenticeship/agent_env_exporter.py +53 -0
- package/src/agent_apprenticeship/agent_env_verify.py +32 -0
- package/src/agent_apprenticeship/atif.py +152 -0
- package/src/agent_apprenticeship/atif_models/LICENSE +201 -0
- package/src/agent_apprenticeship/atif_models/__init__.py +38 -0
- package/src/agent_apprenticeship/atif_models/agent.py +35 -0
- package/src/agent_apprenticeship/atif_models/content.py +159 -0
- package/src/agent_apprenticeship/atif_models/final_metrics.py +43 -0
- package/src/agent_apprenticeship/atif_models/metrics.py +47 -0
- package/src/agent_apprenticeship/atif_models/observation.py +19 -0
- package/src/agent_apprenticeship/atif_models/observation_result.py +45 -0
- package/src/agent_apprenticeship/atif_models/step.py +142 -0
- package/src/agent_apprenticeship/atif_models/subagent_trajectory_ref.py +88 -0
- package/src/agent_apprenticeship/atif_models/tool_call.py +34 -0
- package/src/agent_apprenticeship/atif_models/trajectory.py +196 -0
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/batch_runner.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/bundle_exporter.py +8 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/certification.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/cli.py +26 -7
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/codex_runner.py +17 -17
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/config.py +2 -2
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/ecosystem_r2.py +3 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/evaluator.py +8 -27
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/grader.py +6 -5
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/integration_status.py +1 -1
- package/src/agent_apprenticeship/io.py +104 -0
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/learning.py +7 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/loop.py +5 -5
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/loop_review.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/mentor_checkpoints.py +2 -2
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/package_exporter.py +5 -3
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/progress.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/public_run.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/public_sanitizer.py +2 -2
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/release_exporter.py +7 -4
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/rubric_generation.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/schemas.py +17 -4
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/session_events.py +4 -4
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/task_intake.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/tdo.py +5 -1
- package/src/agent_apprenticeship/trace_normalizer.py +103 -0
- package/src/agent_apprenticeship/trace_prompt.py +48 -0
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/training_signals.py +1 -1
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/validation.py +3 -3
- package/src/{agent_apprenticeship_trace → agent_apprenticeship}/verifier.py +6 -5
- package/docs/bioactive_evidence_mapping_schema.md +0 -113
- package/docs/dataset_schema.md +0 -160
- package/docs/local_usage.md +0 -191
- package/docs/scaling_to_1000_tasks.md +0 -3
- package/docs/trace_schema.md +0 -192
- package/docs/v0.2_release_notes.md +0 -61
- package/src/agent_apprenticeship_trace/io.py +0 -20
- package/src/agent_apprenticeship_trace/trace_normalizer.py +0 -1026
- package/src/agent_apprenticeship_trace/trace_prompt.py +0 -78
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/actual_outputs_normalizer.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/apprentice_adapters.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/artifact_capture.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/artifact_previews.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/artifact_resolver.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/command_discovery.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/contract_diagnostics.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/contract_verifier.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/env.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/lesson_extractor.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/llm_output_normalizer.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/openai_structured.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/recipes.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/revision.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/role_runners.py +0 -0
- /package/src/{agent_apprenticeship_trace → agent_apprenticeship}/version.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Final metrics model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FinalMetrics(BaseModel):
|
|
12
|
+
"""Aggregate statistics for the entire trajectory."""
|
|
13
|
+
|
|
14
|
+
total_prompt_tokens: int | None = Field(
|
|
15
|
+
default=None,
|
|
16
|
+
description="Sum of all prompt tokens across all steps, including cached tokens",
|
|
17
|
+
)
|
|
18
|
+
total_completion_tokens: int | None = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Sum of all completion tokens across all steps",
|
|
21
|
+
)
|
|
22
|
+
total_cached_tokens: int | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Sum of all cached tokens across all steps",
|
|
25
|
+
)
|
|
26
|
+
total_cost_usd: float | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Total real monetary cost for the entire trajectory, including cost for subagents, if any",
|
|
29
|
+
)
|
|
30
|
+
total_steps: int | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
ge=0,
|
|
33
|
+
description=(
|
|
34
|
+
"Total number of steps. If not equivalent to the number of steps in the "
|
|
35
|
+
"trajectory, must be documented in the root-level notes field."
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
extra: dict[str, Any] | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Custom aggregate metrics",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
model_config = {"extra": "forbid"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Metrics model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Metrics(BaseModel):
|
|
12
|
+
"""LLM operational and confidence data."""
|
|
13
|
+
|
|
14
|
+
prompt_tokens: int | None = Field(
|
|
15
|
+
default=None,
|
|
16
|
+
description="Total input tokens including cached and non-cached",
|
|
17
|
+
)
|
|
18
|
+
completion_tokens: int | None = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Total tokens generated by the LLM response",
|
|
21
|
+
)
|
|
22
|
+
cached_tokens: int | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Subset of prompt_tokens that were cache hits",
|
|
25
|
+
)
|
|
26
|
+
cost_usd: float | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Monetary cost of the API call in USD",
|
|
29
|
+
)
|
|
30
|
+
prompt_token_ids: list[int] | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Token IDs for prompt (input) tokens sent to the LLM, including chat history (if applicable)",
|
|
33
|
+
)
|
|
34
|
+
completion_token_ids: list[int] | None = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="Token IDs for completion (response) tokens, enabling RL training without retokenization drift",
|
|
37
|
+
)
|
|
38
|
+
logprobs: list[float] | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Log probability assigned to each generated token",
|
|
41
|
+
)
|
|
42
|
+
extra: dict[str, Any] | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Other metrics",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
model_config = {"extra": "forbid"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Observation model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from agent_apprenticeship.atif_models.observation_result import ObservationResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Observation(BaseModel):
|
|
12
|
+
"""Environment feedback/result after actions or system events."""
|
|
13
|
+
|
|
14
|
+
results: list[ObservationResult] = Field(
|
|
15
|
+
default=...,
|
|
16
|
+
description="Array of result objects from tool calls or actions",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
model_config = {"extra": "forbid"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Observation result model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from agent_apprenticeship.atif_models.content import ContentPart
|
|
11
|
+
from agent_apprenticeship.atif_models.subagent_trajectory_ref import SubagentTrajectoryRef
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ObservationResult(BaseModel):
|
|
15
|
+
"""A single result within an observation."""
|
|
16
|
+
|
|
17
|
+
source_call_id: str | None = Field(
|
|
18
|
+
default=None,
|
|
19
|
+
description=(
|
|
20
|
+
"The `tool_call_id` from the _tool_calls_ array in _StepObject_ that this "
|
|
21
|
+
"result corresponds to. If null or omitted, the result comes from an "
|
|
22
|
+
"action that doesn't use the standard tool calling format (e.g., agent "
|
|
23
|
+
"actions without tool calls or system-initiated operations)."
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
content: str | list[ContentPart] | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description=(
|
|
29
|
+
"The output or result from the tool execution. String for text-only "
|
|
30
|
+
"content, or array of ContentPart for multimodal content (added in ATIF-v1.6)."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
subagent_trajectory_ref: list[SubagentTrajectoryRef] | None = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="Array of references to delegated subagent trajectories",
|
|
36
|
+
)
|
|
37
|
+
extra: dict[str, Any] | None = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description=(
|
|
40
|
+
"Custom observation-result-level metadata (e.g., confidence score, "
|
|
41
|
+
"retrieval score, source document ID). Added in ATIF-v1.7."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
model_config = {"extra": "forbid"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Step model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
10
|
+
|
|
11
|
+
from agent_apprenticeship.atif_models.content import ContentPart
|
|
12
|
+
from agent_apprenticeship.atif_models.metrics import Metrics
|
|
13
|
+
from agent_apprenticeship.atif_models.observation import Observation
|
|
14
|
+
from agent_apprenticeship.atif_models.tool_call import ToolCall
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Step(BaseModel):
|
|
18
|
+
"""A single step in the trajectory."""
|
|
19
|
+
|
|
20
|
+
step_id: int = Field(
|
|
21
|
+
default=...,
|
|
22
|
+
ge=1,
|
|
23
|
+
description="Ordinal index of the turn (starting from 1)",
|
|
24
|
+
)
|
|
25
|
+
timestamp: str | None = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="ISO 8601 timestamp indicating when this step occurred",
|
|
28
|
+
)
|
|
29
|
+
source: Literal["system", "user", "agent"] = Field(
|
|
30
|
+
default=...,
|
|
31
|
+
description="The originator of this step",
|
|
32
|
+
)
|
|
33
|
+
model_name: str | None = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description=(
|
|
36
|
+
"The specific LLM model used for this turn. Omission implies the model "
|
|
37
|
+
"defined in the root-level agent config."
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
reasoning_effort: str | float | None = Field(
|
|
41
|
+
default=None,
|
|
42
|
+
description="Qualitative or quantitative measure of effort",
|
|
43
|
+
)
|
|
44
|
+
message: str | list[ContentPart] = Field(
|
|
45
|
+
default=...,
|
|
46
|
+
description=(
|
|
47
|
+
"The dialogue message. String for text-only content, or array of "
|
|
48
|
+
"ContentPart for multimodal content (added in ATIF-v1.6)."
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
reasoning_content: str | None = Field(
|
|
52
|
+
default=None,
|
|
53
|
+
description="The agent's explicit internal reasoning",
|
|
54
|
+
)
|
|
55
|
+
tool_calls: list[ToolCall] | None = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="Array of structured objects for the agent's actions",
|
|
58
|
+
)
|
|
59
|
+
observation: Observation | None = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="Environment feedback/result after actions or system events",
|
|
62
|
+
)
|
|
63
|
+
metrics: Metrics | None = Field(
|
|
64
|
+
default=None,
|
|
65
|
+
description="LLM operational and confidence data for this step",
|
|
66
|
+
)
|
|
67
|
+
is_copied_context: bool | None = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
description=(
|
|
70
|
+
"Indicates whether this step was copied from a previous trajectory "
|
|
71
|
+
"for context (e.g., during continuation after summarization). "
|
|
72
|
+
"Steps marked as copied context should not be included in training data "
|
|
73
|
+
"as they represent previously-trained interactions. "
|
|
74
|
+
"Added in ATIF-v1.5."
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
llm_call_count: int | None = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
ge=0,
|
|
80
|
+
description=(
|
|
81
|
+
"Number of LLM inferences this step represents. When >1, metrics are "
|
|
82
|
+
"aggregated across multiple LLM calls. When 1, the step represents exactly "
|
|
83
|
+
"one inference. When 0 on a `source: 'agent'` step, the step represents a "
|
|
84
|
+
"deterministic (non-LLM) dispatch; `metrics` and `reasoning_content` MUST "
|
|
85
|
+
"be absent in that case. When null, the producer did not track this "
|
|
86
|
+
"(backward-compatible default). Added in ATIF-v1.7."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
extra: dict[str, Any] | None = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description="Custom step-level metadata",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
model_config = {"extra": "forbid"}
|
|
95
|
+
|
|
96
|
+
@field_validator("timestamp")
|
|
97
|
+
@classmethod
|
|
98
|
+
def validate_timestamp(cls, v: str | None) -> str | None:
|
|
99
|
+
"""Validate that timestamp is a valid ISO 8601 string."""
|
|
100
|
+
if v is not None:
|
|
101
|
+
try:
|
|
102
|
+
datetime.fromisoformat(v.replace("Z", "+00:00"))
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
raise ValueError(f"Invalid ISO 8601 timestamp: {e}")
|
|
105
|
+
return v
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def validate_agent_only_fields(self) -> "Step":
|
|
109
|
+
"""Validate that certain fields are only present for agent steps."""
|
|
110
|
+
if self.source != "agent":
|
|
111
|
+
agent_only_fields = [
|
|
112
|
+
"model_name",
|
|
113
|
+
"reasoning_effort",
|
|
114
|
+
"reasoning_content",
|
|
115
|
+
"tool_calls",
|
|
116
|
+
"metrics",
|
|
117
|
+
]
|
|
118
|
+
for field in agent_only_fields:
|
|
119
|
+
if getattr(self, field) is not None:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"Field '{field}' is only applicable when source is 'agent', "
|
|
122
|
+
f"but source is '{self.source}'"
|
|
123
|
+
)
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
@model_validator(mode="after")
|
|
127
|
+
def validate_llm_call_count_zero_fields(self) -> "Step":
|
|
128
|
+
"""Enforce ATIF v1.7 no-LLM orchestration rule.
|
|
129
|
+
|
|
130
|
+
When ``llm_call_count == 0`` on a ``source: "agent"`` step, the step
|
|
131
|
+
represents a deterministic (non-LLM) dispatch. LLM-specific fields
|
|
132
|
+
(``metrics``, ``reasoning_content``) MUST be absent on such steps.
|
|
133
|
+
"""
|
|
134
|
+
if self.llm_call_count == 0 and self.source == "agent":
|
|
135
|
+
llm_only_fields = ["metrics", "reasoning_content"]
|
|
136
|
+
for field in llm_only_fields:
|
|
137
|
+
if getattr(self, field) is not None:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"Field '{field}' must be absent when llm_call_count is 0 "
|
|
140
|
+
f"(deterministic dispatch on a 'source: agent' step)"
|
|
141
|
+
)
|
|
142
|
+
return self
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Subagent trajectory reference model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SubagentTrajectoryRef(BaseModel):
|
|
12
|
+
"""Reference to a delegated subagent trajectory.
|
|
13
|
+
|
|
14
|
+
A subagent reference is resolved by one of two mechanisms:
|
|
15
|
+
|
|
16
|
+
1. **Embedded form** — set `trajectory_id` to match the
|
|
17
|
+
`Trajectory.trajectory_id` of an entry in the parent's
|
|
18
|
+
`subagent_trajectories` array.
|
|
19
|
+
2. **File-ref form** — set `trajectory_path` to the location
|
|
20
|
+
(file path, S3 URL, etc.) of an external trajectory file.
|
|
21
|
+
|
|
22
|
+
These two mechanisms are the only resolution keys. `session_id`, when
|
|
23
|
+
present on the ref, is **informational only**: it records the run
|
|
24
|
+
identity of the delegated subagent for debug / correlation / cross-
|
|
25
|
+
trajectory search purposes, and MUST NOT be used as a matching key
|
|
26
|
+
(it is run-scoped and MAY collide across siblings — see
|
|
27
|
+
`Trajectory.session_id`). A ref therefore MUST set at least one of
|
|
28
|
+
`trajectory_id` or `trajectory_path`; `session_id` alone is not a
|
|
29
|
+
resolvable reference.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
trajectory_id: str | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description=(
|
|
35
|
+
"Canonical identifier of the delegated subagent trajectory. "
|
|
36
|
+
"Matches `Trajectory.trajectory_id` of an entry in the parent's "
|
|
37
|
+
"`subagent_trajectories` array and is the resolution key for "
|
|
38
|
+
"embedded references. Added in ATIF-v1.7 to provide a document-"
|
|
39
|
+
"unique matching key without overloading `session_id`."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
session_id: str | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description=(
|
|
45
|
+
"Run identity of the delegated subagent trajectory. Informational "
|
|
46
|
+
"only: recorded so consumers can correlate this ref back to the "
|
|
47
|
+
"subagent's run for debug / search / display purposes. Run-scoped "
|
|
48
|
+
"(see `Trajectory.session_id`) and therefore NOT a valid "
|
|
49
|
+
"resolution key — consumers MUST resolve via `trajectory_id` "
|
|
50
|
+
"(embedded) or `trajectory_path` (external file)."
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
trajectory_path: str | None = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description=(
|
|
56
|
+
"Location of the complete subagent trajectory as an external "
|
|
57
|
+
"file (file path, S3 URL, database reference, etc.). Resolution "
|
|
58
|
+
"key for file-ref references. When both `trajectory_id` and "
|
|
59
|
+
"`trajectory_path` are set, consumers MAY choose either; "
|
|
60
|
+
"typically `trajectory_id` is preferred when the embedded "
|
|
61
|
+
"trajectory is available in-memory."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
extra: dict[str, Any] | None = Field(
|
|
65
|
+
default=None,
|
|
66
|
+
description="Custom metadata about the subagent execution",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
model_config = {"extra": "forbid"}
|
|
70
|
+
|
|
71
|
+
@model_validator(mode="after")
|
|
72
|
+
def validate_is_resolvable(self) -> "SubagentTrajectoryRef":
|
|
73
|
+
"""A ref must be resolvable: set `trajectory_id` or `trajectory_path`.
|
|
74
|
+
|
|
75
|
+
`session_id` alone is not sufficient because it is run-scoped and
|
|
76
|
+
MAY collide across siblings (see `Trajectory.session_id`), so it
|
|
77
|
+
cannot unambiguously identify which subagent trajectory a ref
|
|
78
|
+
points at.
|
|
79
|
+
"""
|
|
80
|
+
if self.trajectory_id is None and self.trajectory_path is None:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"SubagentTrajectoryRef must be resolvable: set either "
|
|
83
|
+
"`trajectory_id` (for embedded references) or "
|
|
84
|
+
"`trajectory_path` (for external-file references). "
|
|
85
|
+
"`session_id` alone is not a resolution key — it is "
|
|
86
|
+
"run-scoped and may collide across siblings."
|
|
87
|
+
)
|
|
88
|
+
return self
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Tool call model for ATIF trajectories."""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ToolCall(BaseModel):
|
|
12
|
+
"""A tool call within a step."""
|
|
13
|
+
|
|
14
|
+
tool_call_id: str = Field(
|
|
15
|
+
default=...,
|
|
16
|
+
description="Unique identifier for this specific tool call",
|
|
17
|
+
)
|
|
18
|
+
function_name: str = Field(
|
|
19
|
+
default=...,
|
|
20
|
+
description="The name of the function or tool being invoked",
|
|
21
|
+
)
|
|
22
|
+
arguments: dict[str, Any] = Field(
|
|
23
|
+
default=...,
|
|
24
|
+
description="Arguments passed to the function (can be empty dict)",
|
|
25
|
+
)
|
|
26
|
+
extra: dict[str, Any] | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description=(
|
|
29
|
+
"Custom tool-call-level metadata (e.g., timeout, retry count, tool version). "
|
|
30
|
+
"Added in ATIF-v1.7."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
model_config = {"extra": "forbid"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Harbor ATIF models (Apache-2.0; see LICENSE).
|
|
2
|
+
# Source: harbor-framework/harbor@357c5e74020fc0890d69fb3a7db610896b9ce57d
|
|
3
|
+
# Modified: local imports; trajectory schema version pinned to v1.8.
|
|
4
|
+
"""Trajectory model for ATIF (Agent Trajectory Interchange Format)."""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
|
+
|
|
10
|
+
from agent_apprenticeship.atif_models.agent import Agent
|
|
11
|
+
from agent_apprenticeship.atif_models.final_metrics import FinalMetrics
|
|
12
|
+
from agent_apprenticeship.atif_models.step import Step
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Trajectory(BaseModel):
|
|
16
|
+
"""Agent Trajectory in ATIF (Agent Trajectory Interchange Format)."""
|
|
17
|
+
|
|
18
|
+
schema_version: Literal["ATIF-v1.8"] = "ATIF-v1.8"
|
|
19
|
+
session_id: str | None = Field(
|
|
20
|
+
default=None,
|
|
21
|
+
description=(
|
|
22
|
+
"Identifier for the agent run this trajectory belongs to. Scoped "
|
|
23
|
+
"to the run, NOT to an individual trajectory document: multiple "
|
|
24
|
+
"Trajectory objects MAY share the same `session_id` when they "
|
|
25
|
+
"represent the same logical run (e.g., a parent trajectory and "
|
|
26
|
+
"its embedded subagents, or a trajectory and its continuation "
|
|
27
|
+
"segments linked via `continued_trajectory_ref`). Therefore "
|
|
28
|
+
"`session_id`s within a parent's `subagent_trajectories` array "
|
|
29
|
+
"are NOT required to be unique. Use `trajectory_id` when a "
|
|
30
|
+
"per-trajectory-document unique identifier is required (e.g., "
|
|
31
|
+
"for `SubagentTrajectoryRef` resolution). Optional since "
|
|
32
|
+
"ATIF-v1.7; producers SHOULD set this on root trajectories for "
|
|
33
|
+
"run-level traceability, and MAY omit it on embedded subagents "
|
|
34
|
+
"that inherit the parent's run identity."
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
trajectory_id: str | None = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description=(
|
|
40
|
+
"Canonical per-trajectory-document identifier, distinct from "
|
|
41
|
+
"`session_id`. Unlike `session_id` (which is run-scoped and MAY "
|
|
42
|
+
"be shared), `trajectory_id` uniquely identifies THIS trajectory "
|
|
43
|
+
"object. Used to resolve `SubagentTrajectoryRef` entries against "
|
|
44
|
+
"the root's `subagent_trajectories` array without overloading "
|
|
45
|
+
"`session_id`'s run-scoped semantics. Optional on standalone "
|
|
46
|
+
"trajectories, but REQUIRED on any trajectory embedded in a "
|
|
47
|
+
"parent's `subagent_trajectories` array. `trajectory_id`s within "
|
|
48
|
+
"a single parent's `subagent_trajectories` array MUST be unique. "
|
|
49
|
+
"Added in ATIF-v1.7."
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
agent: Agent = Field(
|
|
53
|
+
default=...,
|
|
54
|
+
description="Object specifying the agent configuration",
|
|
55
|
+
)
|
|
56
|
+
steps: list[Step] = Field(
|
|
57
|
+
default=...,
|
|
58
|
+
min_length=1,
|
|
59
|
+
description="Array of step objects representing the complete interaction history",
|
|
60
|
+
)
|
|
61
|
+
notes: str | None = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description="Custom information, design notes, or explanations",
|
|
64
|
+
)
|
|
65
|
+
final_metrics: FinalMetrics | None = Field(
|
|
66
|
+
default=None,
|
|
67
|
+
description="Summary metrics for the entire trajectory",
|
|
68
|
+
)
|
|
69
|
+
continued_trajectory_ref: str | None = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Reference to the continuation trajectory file if this trajectory is continued in another file",
|
|
72
|
+
)
|
|
73
|
+
extra: dict[str, Any] | None = Field(
|
|
74
|
+
default=None,
|
|
75
|
+
description="Custom root-level metadata",
|
|
76
|
+
)
|
|
77
|
+
subagent_trajectories: list["Trajectory"] | None = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
description=(
|
|
80
|
+
"Array of embedded subagent trajectories. Each element is a complete, "
|
|
81
|
+
"independently-valid ATIF Trajectory with its own schema_version, "
|
|
82
|
+
"agent, and step_id sequence starting at 1. Enables single-file "
|
|
83
|
+
"storage of multi-agent workflows: when a "
|
|
84
|
+
"SubagentTrajectoryRef.trajectory_path is null, consumers resolve "
|
|
85
|
+
"the reference by matching SubagentTrajectoryRef.trajectory_id "
|
|
86
|
+
"against Trajectory.trajectory_id of entries in this array. "
|
|
87
|
+
"Uniqueness rules: every embedded subagent MUST set `trajectory_id`, "
|
|
88
|
+
"and `trajectory_id`s within this array MUST be unique. "
|
|
89
|
+
"`session_id`, by contrast, is run-scoped and MAY collide across "
|
|
90
|
+
"siblings (or match the parent) when all trajectories belong to "
|
|
91
|
+
"the same logical run; embedded subagents MAY also omit "
|
|
92
|
+
"`session_id` entirely to inherit the parent's run identity. "
|
|
93
|
+
"Added in ATIF-v1.7."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
model_config = {"extra": "forbid"}
|
|
98
|
+
|
|
99
|
+
def to_json_dict(self, exclude_none: bool = True) -> dict[str, Any]:
|
|
100
|
+
"""Export trajectory to a dictionary suitable for JSON serialization.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
exclude_none: If True, exclude fields with None values from output.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Dictionary representation of the trajectory.
|
|
107
|
+
"""
|
|
108
|
+
return self.model_dump(exclude_none=exclude_none, mode="json")
|
|
109
|
+
|
|
110
|
+
@model_validator(mode="after")
|
|
111
|
+
def validate_step_ids(self) -> "Trajectory":
|
|
112
|
+
"""Validate that step_ids are sequential starting from 1."""
|
|
113
|
+
for i, step in enumerate(self.steps):
|
|
114
|
+
expected_step_id = i + 1
|
|
115
|
+
if step.step_id != expected_step_id:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"steps[{i}].step_id: expected {expected_step_id} "
|
|
118
|
+
f"(sequential from 1), got {step.step_id}"
|
|
119
|
+
)
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
@model_validator(mode="after")
|
|
123
|
+
def validate_embedded_subagent_trajectory_ids(self) -> "Trajectory":
|
|
124
|
+
"""Every embedded subagent must carry a unique, non-null `trajectory_id`.
|
|
125
|
+
|
|
126
|
+
Embedded subagents are resolved by matching
|
|
127
|
+
`SubagentTrajectoryRef.trajectory_id` against
|
|
128
|
+
`Trajectory.trajectory_id`, so the identifier must be present and
|
|
129
|
+
unique within a parent's `subagent_trajectories` array. Note that
|
|
130
|
+
no such constraint is placed on `session_id` — siblings MAY share
|
|
131
|
+
a `session_id` (or omit it entirely to inherit the parent's) when
|
|
132
|
+
they represent the same logical agent run.
|
|
133
|
+
"""
|
|
134
|
+
if not self.subagent_trajectories:
|
|
135
|
+
return self
|
|
136
|
+
seen: set[str] = set()
|
|
137
|
+
for i, sub in enumerate(self.subagent_trajectories):
|
|
138
|
+
if sub.trajectory_id is None:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"subagent_trajectories[{i}].trajectory_id is required "
|
|
141
|
+
f"for embedded subagents "
|
|
142
|
+
f"(agent.name={sub.agent.name!r}, "
|
|
143
|
+
f"session_id={sub.session_id!r})"
|
|
144
|
+
)
|
|
145
|
+
if sub.trajectory_id in seen:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"subagent_trajectories[{i}].trajectory_id "
|
|
148
|
+
f"{sub.trajectory_id!r} is not unique within "
|
|
149
|
+
f"subagent_trajectories"
|
|
150
|
+
)
|
|
151
|
+
seen.add(sub.trajectory_id)
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
@model_validator(mode="after")
|
|
155
|
+
def validate_tool_call_references(self) -> "Trajectory":
|
|
156
|
+
"""Validate that observation source_call_ids reference valid tool_call_ids."""
|
|
157
|
+
for step in self.steps:
|
|
158
|
+
if step.observation is None:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Collect all tool_call_ids from this step
|
|
162
|
+
tool_call_ids = set()
|
|
163
|
+
if step.tool_calls:
|
|
164
|
+
tool_call_ids = {tc.tool_call_id for tc in step.tool_calls}
|
|
165
|
+
|
|
166
|
+
# Check that source_call_ids reference valid tool_call_ids
|
|
167
|
+
for result in step.observation.results:
|
|
168
|
+
if result.source_call_id is not None:
|
|
169
|
+
if result.source_call_id not in tool_call_ids:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"Observation result references source_call_id "
|
|
172
|
+
f"'{result.source_call_id}' which is not found in "
|
|
173
|
+
f"step {step.step_id}'s tool_calls"
|
|
174
|
+
)
|
|
175
|
+
return self
|
|
176
|
+
|
|
177
|
+
def has_multimodal_content(self) -> bool:
|
|
178
|
+
"""Check if any step contains multimodal content (images or audio).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
True if any step message or observation contains a media content
|
|
182
|
+
part. Audio counts since ATIF-v1.8.
|
|
183
|
+
"""
|
|
184
|
+
media_types = {"image", "audio"}
|
|
185
|
+
for step in self.steps:
|
|
186
|
+
if isinstance(step.message, list):
|
|
187
|
+
for part in step.message:
|
|
188
|
+
if part.type in media_types:
|
|
189
|
+
return True
|
|
190
|
+
if step.observation:
|
|
191
|
+
for result in step.observation.results:
|
|
192
|
+
if isinstance(result.content, list):
|
|
193
|
+
for part in result.content:
|
|
194
|
+
if part.type in media_types:
|
|
195
|
+
return True
|
|
196
|
+
return False
|
|
@@ -3,7 +3,7 @@ import time, traceback, json
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from .schemas import RawTaskRecord
|
|
6
|
-
from .io import read_jsonl, append_jsonl, write_json
|
|
6
|
+
from .io import read_workflow_jsonl as read_jsonl, append_jsonl, write_json
|
|
7
7
|
from .loop import run_task
|
|
8
8
|
from .release_exporter import create_release
|
|
9
9
|
from .public_sanitizer import classify_provider_failure
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from .io import validate_trace_tree
|
|
3
|
+
from .agent_env_exporter import export_agent_env
|
|
2
4
|
|
|
3
5
|
import shutil
|
|
4
6
|
import tempfile
|
|
@@ -8,7 +10,7 @@ from pathlib import Path
|
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
from .config import Settings, apprenticeship_mode_display, get_settings, normalize_apprenticeship_mode, normalize_mentor_mode
|
|
11
|
-
from .io import append_jsonl, read_json, read_jsonl, write_json
|
|
13
|
+
from .io import append_jsonl, read_workflow_json as read_json, read_workflow_jsonl as read_jsonl, write_json
|
|
12
14
|
from .public_sanitizer import sanitize_public_obj, sanitize_public_text
|
|
13
15
|
from .release_exporter import create_release
|
|
14
16
|
from .tdo import tdo_manifest_fields_from_report
|
|
@@ -461,11 +463,14 @@ def create_contribution_bundle(
|
|
|
461
463
|
shutil.rmtree(bundle_root)
|
|
462
464
|
bundle_root.mkdir(parents=True, exist_ok=True)
|
|
463
465
|
|
|
466
|
+
validate_trace_tree(run_root)
|
|
464
467
|
if release_style:
|
|
465
468
|
create_release(run_root, bundle_root)
|
|
466
469
|
_copy_session_files(run_root, bundle_root, settings)
|
|
467
470
|
_write_manifest(bundle_root, bundle_root, settings, include_internal_schema=True)
|
|
468
471
|
_write_card(bundle_root)
|
|
472
|
+
validate_trace_tree(bundle_root)
|
|
473
|
+
export_agent_env(bundle_root)
|
|
469
474
|
return bundle_root
|
|
470
475
|
|
|
471
476
|
with tempfile.TemporaryDirectory(prefix="aa-release-for-bundle-") as tmp:
|
|
@@ -476,4 +481,6 @@ def create_contribution_bundle(
|
|
|
476
481
|
_write_manifest(bundle_root, release_root, settings, include_internal_schema=include_debug)
|
|
477
482
|
_write_card(bundle_root)
|
|
478
483
|
|
|
484
|
+
validate_trace_tree(bundle_root)
|
|
485
|
+
export_agent_env(bundle_root)
|
|
479
486
|
return bundle_root
|
|
@@ -13,7 +13,7 @@ from .config import get_settings, init_settings, update_settings
|
|
|
13
13
|
from .command_discovery import resolve_command, resolve_agent_command, gui_app_hint
|
|
14
14
|
from .env import redact_secrets
|
|
15
15
|
from .integration_status import record_certification_result
|
|
16
|
-
from .io import read_json
|
|
16
|
+
from .io import read_workflow_json as read_json
|
|
17
17
|
from .openai_structured import run_llm_smoke
|
|
18
18
|
from .public_run import run_prompt_task
|
|
19
19
|
from .recipes import MODEL_PROVIDER_RECIPES, WORKER_AGENT_RECIPES
|