agent-learning 0.4.3__tar.gz → 0.5.1__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.
- {agent_learning-0.4.3/src/agent_learning.egg-info → agent_learning-0.5.1}/PKG-INFO +9 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/PYPI.md +8 -0
- agent_learning-0.5.1/README.md +188 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/pyproject.toml +1 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/_version.py +1 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/cli.py +240 -7
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/storage/base.py +2 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/storage/cosmos.py +18 -2
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/storage/local.py +2 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/storage/memory.py +2 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/training/runner.py +1 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1/src/agent_learning.egg-info}/PKG-INFO +9 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_cli.py +383 -2
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_end_to_end.py +28 -2
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_storage_local.py +9 -1
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_storage_memory.py +9 -1
- agent_learning-0.4.3/README.md +0 -82
- {agent_learning-0.4.3 → agent_learning-0.5.1}/LICENSE +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/setup.cfg +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/capture.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/router.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/scorers/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/scorers/_base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/scorers/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/scorers/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/classifiers/scorers/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/config.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/learners/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/learners/base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/learners/reinforce.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/intent_resolution.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/local.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/registry.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/task_adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/metrics/task_completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/policy/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/policy/base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/policy/contextual_softmax.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/policy/softmax_bandit.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/py.typed +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/rewards/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/rewards/shaping.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/rewards/writer.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/llm/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/llm/_base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/llm/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/llm/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/llm/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp/_base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp_text/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp_text/_base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp_text/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp_text/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/nlp_text/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/slm/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/slm/_base.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/slm/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/slm/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/slm/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/stdlib/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/stdlib/_text.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/stdlib/adherence.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/stdlib/completion.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/scorers/stdlib/intent.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/storage/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/training/__init__.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning/types.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning.egg-info/SOURCES.txt +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning.egg-info/dependency_links.txt +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning.egg-info/entry_points.txt +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning.egg-info/requires.txt +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/src/agent_learning.egg-info/top_level.txt +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_capture.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_contextual_policy.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_default_store.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_learner.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_metrics_local.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_policy.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_scorers.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_scorers_llm.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_scorers_nlp_text.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_scorers_slm.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_scorers_stdlib.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_shaping.py +0 -0
- {agent_learning-0.4.3 → agent_learning-0.5.1}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-learning
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Native, in-process reinforcement learning SDK for AI agents — in-memory and local-file storage by default.
|
|
5
5
|
Author: Chris Tava
|
|
6
6
|
License: MIT License
|
|
@@ -69,6 +69,10 @@ Dynamic: license-file
|
|
|
69
69
|
|
|
70
70
|
Native reinforcement learning SDK for AI agents. An in-process Learner optimizes a small, interpretable TaskPolicy over discrete agent choices (understand intent and complete task by choosing the right outcome).
|
|
71
71
|
|
|
72
|
+
TaskPolicies model reusable decisions among executable alternatives such as
|
|
73
|
+
models, skills, tools, workflows, or workloads. Factual questions, ordinary
|
|
74
|
+
chat, reporting, and learning automation are not policy tasks.
|
|
75
|
+
|
|
72
76
|
## How it works
|
|
73
77
|
|
|
74
78
|
The SDK improves agents without LLM weight fine-tuning. There are no GPU fine-tune jobs and no opaque update cycles — just three pieces that run in your existing Python process:
|
|
@@ -79,4 +83,8 @@ The SDK improves agents without LLM weight fine-tuning. There are no GPU fine-tu
|
|
|
79
83
|
|
|
80
84
|
3. **Learner** applies REINFORCE-with-baseline to update TaskPolicy logits directly from logged episodes. Updates are tiny gradient steps that run on local compute and persist through a pluggable store — in-memory or local files by default, with Azure Cosmos DB optional.
|
|
81
85
|
|
|
86
|
+
`task-policy-decide` closes the loop at execution time by returning the selected
|
|
87
|
+
action plus historical correctness, reward, result summaries, and per-metric
|
|
88
|
+
quality feedback for the agent to use on its next delegated decision.
|
|
89
|
+
|
|
82
90
|
Every episode, reward, run, and deployment is captured by the configured store — in-memory or local files by default, or Azure Cosmos DB — giving you a complete lineage and audit trail of how the policy evolved over time.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Native reinforcement learning SDK for AI agents. An in-process Learner optimizes a small, interpretable TaskPolicy over discrete agent choices (understand intent and complete task by choosing the right outcome).
|
|
4
4
|
|
|
5
|
+
TaskPolicies model reusable decisions among executable alternatives such as
|
|
6
|
+
models, skills, tools, workflows, or workloads. Factual questions, ordinary
|
|
7
|
+
chat, reporting, and learning automation are not policy tasks.
|
|
8
|
+
|
|
5
9
|
## How it works
|
|
6
10
|
|
|
7
11
|
The SDK improves agents without LLM weight fine-tuning. There are no GPU fine-tune jobs and no opaque update cycles — just three pieces that run in your existing Python process:
|
|
@@ -12,4 +16,8 @@ The SDK improves agents without LLM weight fine-tuning. There are no GPU fine-tu
|
|
|
12
16
|
|
|
13
17
|
3. **Learner** applies REINFORCE-with-baseline to update TaskPolicy logits directly from logged episodes. Updates are tiny gradient steps that run on local compute and persist through a pluggable store — in-memory or local files by default, with Azure Cosmos DB optional.
|
|
14
18
|
|
|
19
|
+
`task-policy-decide` closes the loop at execution time by returning the selected
|
|
20
|
+
action plus historical correctness, reward, result summaries, and per-metric
|
|
21
|
+
quality feedback for the agent to use on its next delegated decision.
|
|
22
|
+
|
|
15
23
|
Every episode, reward, run, and deployment is captured by the configured store — in-memory or local files by default, or Azure Cosmos DB — giving you a complete lineage and audit trail of how the policy evolved over time.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# agent-learning
|
|
2
|
+
|
|
3
|
+
Helping agents make better decisions with measurable feedback.
|
|
4
|
+
|
|
5
|
+
`agent-learning` is a lightweight decision layer for an existing agent. It
|
|
6
|
+
learns which option works best for a recurring task from a small, explicit set
|
|
7
|
+
of executable alternatives, such as prompt variants, retrieval depths, tools,
|
|
8
|
+
models, workflows, or escalation paths.
|
|
9
|
+
|
|
10
|
+
The foundation model stays frozen. There are no GPU training jobs and no
|
|
11
|
+
hidden prompt rewrites. The agent makes an explicit choice, records the
|
|
12
|
+
user-visible outcome, scores the evidence, and applies a small CPU update to
|
|
13
|
+
the next decision.
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="images/agent-decision-making.svg" alt="Animated agentic decision loop: choose a bounded action, execute it, score the observed outcome, and improve the next decision" width="960" style="max-width:100%; height:auto;" />
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
## The decision loop
|
|
20
|
+
|
|
21
|
+
1. **Frame a reusable decision.** Define a stable decision context and at
|
|
22
|
+
least two actions the agent can actually execute. A TaskPolicy owns the
|
|
23
|
+
probability distribution for that `(agent_id, task_id)` pair.
|
|
24
|
+
|
|
25
|
+
2. **Choose and execute.** `task-policy-decide` samples an action from the
|
|
26
|
+
active softmax policy and returns learned feedback from earlier attempts.
|
|
27
|
+
The agent uses that feedback and executes the selected action.
|
|
28
|
+
|
|
29
|
+
3. **Observe and score.** A completed episode preserves the decision context,
|
|
30
|
+
selected action, output, result summary, latency, and independently
|
|
31
|
+
supported correctness evidence. Local scorers measure intent resolution,
|
|
32
|
+
task adherence, and task completion and shape them into one reward.
|
|
33
|
+
|
|
34
|
+
4. **Improve the next decision.** REINFORCE-with-baseline nudges a few policy
|
|
35
|
+
logits, then persists a new policy snapshot. Better-than-usual choices gain
|
|
36
|
+
probability, worse-than-usual choices lose probability, and exploration
|
|
37
|
+
remains available.
|
|
38
|
+
|
|
39
|
+
Everything runs in the existing Python process. Scoring is local by default;
|
|
40
|
+
configured Azure AI evaluators remain available as an opt-in. Stores can be
|
|
41
|
+
in-memory, local JSON files, or Azure Cosmos DB.
|
|
42
|
+
|
|
43
|
+
## What counts as a decision
|
|
44
|
+
|
|
45
|
+
A TaskPolicy is appropriate only when all of these are true:
|
|
46
|
+
|
|
47
|
+
- The agent must choose among at least two explicit executable alternatives.
|
|
48
|
+
- The alternatives are stable enough to reuse on future requests.
|
|
49
|
+
- The choice can affect quality, correctness, latency, cost, safety, or
|
|
50
|
+
completion.
|
|
51
|
+
- An observable outcome can later score whether the choice was useful.
|
|
52
|
+
|
|
53
|
+
Good examples include choosing a retrieval strategy, model, tool, workflow,
|
|
54
|
+
Azure workload, or escalation path for a concrete use case. Factual questions,
|
|
55
|
+
ordinary chat, summaries, reporting, and the learning automation itself are not
|
|
56
|
+
decision policies. Repetition without an executed outcome is not feedback.
|
|
57
|
+
|
|
58
|
+
Before every eligible execution, the decision response includes the selected
|
|
59
|
+
and currently recommended actions, policy version and probability, correctness
|
|
60
|
+
rate, mean reward, metric scores, and recent result summaries. This makes the
|
|
61
|
+
learned evidence useful during execution instead of producing a policy that is
|
|
62
|
+
never consumed.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
### Windows CLI
|
|
67
|
+
|
|
68
|
+
For a Python-independent installation, download `agent-learn.exe` or the
|
|
69
|
+
standalone installer from the
|
|
70
|
+
[latest GitHub release](https://github.com/microsoft/agent-learning/releases/latest).
|
|
71
|
+
The installer can add its installation directory to your user `PATH`, so
|
|
72
|
+
`agent-learn` works from PowerShell or Command Prompt without Python or `pip`.
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
agent-learn.exe --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Python SDK
|
|
79
|
+
|
|
80
|
+
Released versions are published to PyPI:
|
|
81
|
+
<https://pypi.org/project/agent-learning/>.
|
|
82
|
+
|
|
83
|
+
```shell
|
|
84
|
+
pip install agent-learning
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quickstart: improve one recurring decision
|
|
88
|
+
|
|
89
|
+
Use one durable store across CLI processes:
|
|
90
|
+
|
|
91
|
+
```powershell
|
|
92
|
+
$env:AGENT_LEARNING_STORE_BACKEND = "local"
|
|
93
|
+
$env:AGENT_LEARNING_LOCAL_STORE_DIR = Join-Path $env:LOCALAPPDATA "agent-learning\store"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Define actions the agent can really execute in `actions.json`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
[
|
|
100
|
+
{
|
|
101
|
+
"id": "use_text_search",
|
|
102
|
+
"description": "Search for exact symbols and terms",
|
|
103
|
+
"parameters": {"strategy": "text"}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "use_semantic_search",
|
|
107
|
+
"description": "Search the repository by meaning",
|
|
108
|
+
"parameters": {"strategy": "semantic"}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Initialize the decision once, then ask the active policy what to execute:
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
agent-learn task-policy-init `
|
|
117
|
+
--agent-id code-reviewer `
|
|
118
|
+
--task-id choose-repository-search `
|
|
119
|
+
--decision-context "Choose a repository search strategy for a coding task" `
|
|
120
|
+
--actions .\actions.json
|
|
121
|
+
|
|
122
|
+
agent-learn task-policy-decide `
|
|
123
|
+
--agent-id code-reviewer `
|
|
124
|
+
--task-id choose-repository-search
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Execute the returned `selected_action`, preserve its policy fields, and record
|
|
128
|
+
the independently observed outcome in `episode.json`. Then close the loop:
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
agent-learn task-episode-register `
|
|
132
|
+
--agent-id code-reviewer `
|
|
133
|
+
--task-id choose-repository-search `
|
|
134
|
+
--episode .\episode.json `
|
|
135
|
+
--require-decision-policy
|
|
136
|
+
|
|
137
|
+
agent-learn score --agent-id code-reviewer --task-id choose-repository-search
|
|
138
|
+
agent-learn train --agent-id code-reviewer --task-id choose-repository-search --decision-only
|
|
139
|
+
agent-learn task-policy-decide --agent-id code-reviewer --task-id choose-repository-search
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The final decision consumes the updated probabilities and feedback from prior
|
|
143
|
+
executions. Training defaults to a minimum of five completed episodes. See the
|
|
144
|
+
[decision-making guide](docs/decision-making.md) for the episode schema,
|
|
145
|
+
evidence rules, math, and deployment patterns.
|
|
146
|
+
|
|
147
|
+
## Functional Testing
|
|
148
|
+
|
|
149
|
+
A good way to see the SDK in action is to run the
|
|
150
|
+
interactive capture scenario first, followed by the offline batch update:
|
|
151
|
+
|
|
152
|
+
```powershell
|
|
153
|
+
python tests/functional_cli_interactive.py
|
|
154
|
+
python tests/functional_cli_batch.py
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Usage
|
|
158
|
+
|
|
159
|
+
The `agent-learn` CLI provides the decision lifecycle and inspection
|
|
160
|
+
operations:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
agent-learn list
|
|
164
|
+
agent-learn --version
|
|
165
|
+
agent-learn tasks-list <agent_id> [--decision-only]
|
|
166
|
+
agent-learn task-episodes-count <agent_id> [--task-id <task_id>] [--include-incomplete] [--start-date <date>] [--end-date <date>]
|
|
167
|
+
agent-learn task-episodes-list <agent_id> [--task-id <task_id>] [--limit <1-500>] [--include-incomplete] [--start-date <date>] [--end-date <date>]
|
|
168
|
+
agent-learn task-policy-init --agent-id <agent_id> --task-id <task_id> --decision-context <context> --actions ./actions.json
|
|
169
|
+
agent-learn task-policy-decide --agent-id <agent_id> --task-id <task_id> [--history-limit <1-500>] [--greedy] [--seed <integer>]
|
|
170
|
+
agent-learn task-episode-register --agent-id <agent_id> --task-id <task_id> --episode ./episode.json [--require-decision-policy]
|
|
171
|
+
agent-learn score --agent-id <agent_id> [--task-id <task_id>] [--limit <1-500>]
|
|
172
|
+
agent-learn train --agent-id <agent_id> [--task-id <task_id>] [--decision-only] [--limit <1-500>] [--min-episodes <1-500>] [--start-date <date>] [--end-date <date>] [--skip-scoring]
|
|
173
|
+
agent-learn task-policy --agent-id <agent_id> --task-id <task_id>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Documentation
|
|
177
|
+
|
|
178
|
+
- [Agentic decision making](docs/decision-making.md): concepts, evidence,
|
|
179
|
+
workflow, math, and deployment.
|
|
180
|
+
- [Scout decision integration](docs/scout-agent-learn-skill.md): apply learned
|
|
181
|
+
delegated decisions during execution.
|
|
182
|
+
- [Scout decision training](docs/scout-automation-agent-learning.md): train
|
|
183
|
+
eligible policies without turning automation into a policy task.
|
|
184
|
+
- [The math, explained simply](docs/math-explained-simply.md) and
|
|
185
|
+
[mathematical reference](docs/math.md): softmax, rewards, baselines, and
|
|
186
|
+
REINFORCE.
|
|
187
|
+
- [Tiered scoring design](docs/design.md): local and Azure-backed outcome
|
|
188
|
+
scoring.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-learning"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Native, in-process reinforcement learning SDK for AI agents — in-memory and local-file storage by default."
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -5,12 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import math
|
|
9
|
+
import random
|
|
8
10
|
import sys
|
|
9
11
|
import uuid
|
|
10
12
|
from collections import Counter
|
|
11
13
|
from datetime import datetime, timezone
|
|
12
14
|
from typing import Any
|
|
13
15
|
|
|
16
|
+
from ._version import __version__
|
|
14
17
|
from .policy.softmax_bandit import SoftmaxPolicy
|
|
15
18
|
from .storage.cosmos import get_default_store
|
|
16
19
|
from .training.runner import LearningRunner
|
|
@@ -19,6 +22,7 @@ from .types import Action, Episode, MetricName, PolicySnapshot, RewardSource
|
|
|
19
22
|
logger = logging.getLogger(__name__)
|
|
20
23
|
|
|
21
24
|
_MAX_EPISODES = 500
|
|
25
|
+
_DECISION_POLICY_SCOPE = "delegated_decision"
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
def _episode_limit(value: str) -> int:
|
|
@@ -39,13 +43,18 @@ def _iso_date(value: str) -> str:
|
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def _build_arg_parser() -> argparse.ArgumentParser:
|
|
42
|
-
parser = argparse.ArgumentParser(
|
|
46
|
+
parser = argparse.ArgumentParser(
|
|
47
|
+
prog="agent-learn",
|
|
48
|
+
description=f"Native RL CLI for AI agents. SDK version {__version__}.",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
43
51
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
44
52
|
|
|
45
53
|
sub.add_parser("list", help="List discovered agent ids and names.")
|
|
46
54
|
|
|
47
55
|
tasks = sub.add_parser("tasks-list", help="List tasks for an agent.")
|
|
48
56
|
tasks.add_argument("agent_id")
|
|
57
|
+
tasks.add_argument("--decision-only", action="store_true")
|
|
49
58
|
|
|
50
59
|
count = sub.add_parser(
|
|
51
60
|
"task-episodes-count",
|
|
@@ -53,6 +62,11 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
53
62
|
)
|
|
54
63
|
count.add_argument("agent_id")
|
|
55
64
|
count.add_argument("--task-id")
|
|
65
|
+
count.add_argument(
|
|
66
|
+
"--include-incomplete",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Count pending and incomplete attempts as well as full learning episodes.",
|
|
69
|
+
)
|
|
56
70
|
count.add_argument("--start-date", type=_iso_date)
|
|
57
71
|
count.add_argument("--end-date", type=_iso_date)
|
|
58
72
|
|
|
@@ -72,6 +86,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
72
86
|
train.add_argument("--task-id")
|
|
73
87
|
train.add_argument("--limit", type=_episode_limit, default=200)
|
|
74
88
|
train.add_argument("--min-episodes", type=_episode_limit, default=1)
|
|
89
|
+
train.add_argument("--decision-only", action="store_true")
|
|
75
90
|
train.add_argument("--start-date", type=_iso_date)
|
|
76
91
|
train.add_argument("--end-date", type=_iso_date)
|
|
77
92
|
train.add_argument(
|
|
@@ -89,12 +104,27 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
89
104
|
show.add_argument("--agent-id", required=True)
|
|
90
105
|
show.add_argument("--task-id", required=True)
|
|
91
106
|
|
|
107
|
+
decide = sub.add_parser(
|
|
108
|
+
"task-policy-decide",
|
|
109
|
+
help="Choose a delegated decision action and return learned feedback.",
|
|
110
|
+
)
|
|
111
|
+
decide.add_argument("--agent-id", required=True)
|
|
112
|
+
decide.add_argument("--task-id", required=True)
|
|
113
|
+
decide.add_argument("--history-limit", type=_episode_limit, default=100)
|
|
114
|
+
decide.add_argument("--greedy", action="store_true")
|
|
115
|
+
decide.add_argument("--seed", type=int)
|
|
116
|
+
|
|
92
117
|
init = sub.add_parser(
|
|
93
118
|
"task-policy-init",
|
|
94
119
|
help="Create and activate the initial policy for an agent task.",
|
|
95
120
|
)
|
|
96
121
|
init.add_argument("--agent-id", required=True)
|
|
97
122
|
init.add_argument("--task-id", required=True)
|
|
123
|
+
init.add_argument(
|
|
124
|
+
"--decision-context",
|
|
125
|
+
required=True,
|
|
126
|
+
help="Stable description of the delegated choice this policy controls.",
|
|
127
|
+
)
|
|
98
128
|
init.add_argument(
|
|
99
129
|
"--actions",
|
|
100
130
|
required=True,
|
|
@@ -107,6 +137,7 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
107
137
|
)
|
|
108
138
|
register.add_argument("--agent-id", required=True)
|
|
109
139
|
register.add_argument("--task-id", required=True)
|
|
140
|
+
register.add_argument("--require-decision-policy", action="store_true")
|
|
110
141
|
register.add_argument(
|
|
111
142
|
"--episode",
|
|
112
143
|
required=True,
|
|
@@ -124,7 +155,14 @@ def _cmd_agents_list(args: argparse.Namespace) -> int:
|
|
|
124
155
|
|
|
125
156
|
|
|
126
157
|
def _cmd_agent_tasks_list(args: argparse.Namespace) -> int:
|
|
127
|
-
|
|
158
|
+
store = get_default_store()
|
|
159
|
+
tasks = store.list_agent_tasks(args.agent_id)
|
|
160
|
+
if args.decision_only:
|
|
161
|
+
tasks = [
|
|
162
|
+
task
|
|
163
|
+
for task in tasks
|
|
164
|
+
if _is_decision_policy(store.get_active_policy(args.agent_id, task.id))
|
|
165
|
+
]
|
|
128
166
|
print(json.dumps([{"id": task.id, "name": task.name} for task in tasks], indent=2))
|
|
129
167
|
return 0
|
|
130
168
|
|
|
@@ -133,7 +171,7 @@ def _cmd_agents_episodes_count(args: argparse.Namespace) -> int:
|
|
|
133
171
|
count = get_default_store().count_episodes(
|
|
134
172
|
args.agent_id,
|
|
135
173
|
task_id=args.task_id,
|
|
136
|
-
full_only=
|
|
174
|
+
full_only=not args.include_incomplete,
|
|
137
175
|
start_date=args.start_date,
|
|
138
176
|
end_date=args.end_date,
|
|
139
177
|
)
|
|
@@ -149,9 +187,8 @@ def _cmd_agents_episodes_list(args: argparse.Namespace) -> int:
|
|
|
149
187
|
limit=_MAX_EPISODES,
|
|
150
188
|
start_date=args.start_date,
|
|
151
189
|
end_date=args.end_date,
|
|
190
|
+
full_only=not args.include_incomplete,
|
|
152
191
|
)
|
|
153
|
-
if not args.include_incomplete:
|
|
154
|
-
episodes = [episode for episode in episodes if episode.is_full]
|
|
155
192
|
|
|
156
193
|
payload = []
|
|
157
194
|
for episode in episodes[: args.limit]:
|
|
@@ -190,6 +227,7 @@ def _cmd_train(args: argparse.Namespace) -> int:
|
|
|
190
227
|
limit=args.limit,
|
|
191
228
|
start_date=args.start_date,
|
|
192
229
|
end_date=args.end_date,
|
|
230
|
+
full_only=True,
|
|
193
231
|
)
|
|
194
232
|
episode_limits = Counter(episode.task_id for episode in selected_episodes)
|
|
195
233
|
runs: list[dict[str, Any]] = []
|
|
@@ -199,6 +237,9 @@ def _cmd_train(args: argparse.Namespace) -> int:
|
|
|
199
237
|
if snapshot is None:
|
|
200
238
|
skipped.append({"task_id": task_id, "reason": "no active policy"})
|
|
201
239
|
continue
|
|
240
|
+
if args.decision_only and not _is_decision_policy(snapshot):
|
|
241
|
+
skipped.append({"task_id": task_id, "reason": "not a delegated decision policy"})
|
|
242
|
+
continue
|
|
202
243
|
episode_limit = episode_limits.get(task_id, 0)
|
|
203
244
|
if episode_limit == 0:
|
|
204
245
|
skipped.append({"task_id": task_id, "reason": "no episodes in selected batch"})
|
|
@@ -244,6 +285,7 @@ def _cmd_score(args: argparse.Namespace) -> int:
|
|
|
244
285
|
args.agent_id,
|
|
245
286
|
task_id=args.task_id,
|
|
246
287
|
limit=args.limit,
|
|
288
|
+
full_only=True,
|
|
247
289
|
)
|
|
248
290
|
scored = 0
|
|
249
291
|
for episode in episodes:
|
|
@@ -266,6 +308,89 @@ def _policy_payload(snapshot: PolicySnapshot) -> dict[str, Any]:
|
|
|
266
308
|
return payload
|
|
267
309
|
|
|
268
310
|
|
|
311
|
+
def _is_decision_policy(snapshot: PolicySnapshot | None) -> bool:
|
|
312
|
+
return bool(
|
|
313
|
+
snapshot
|
|
314
|
+
and snapshot.metadata.get("policy_scope") == _DECISION_POLICY_SCOPE
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _latest_aggregate(store: Any, episode: Episode) -> float | None:
|
|
319
|
+
rewards = [
|
|
320
|
+
reward
|
|
321
|
+
for reward in store.get_rewards_for_episode(episode.id, episode.agent_id)
|
|
322
|
+
if reward.source == RewardSource.AGGREGATE
|
|
323
|
+
]
|
|
324
|
+
if not rewards:
|
|
325
|
+
return None
|
|
326
|
+
return max(rewards, key=lambda reward: reward.created_at).value
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _decision_feedback(
|
|
330
|
+
store: Any, snapshot: PolicySnapshot, history_limit: int
|
|
331
|
+
) -> dict[str, Any]:
|
|
332
|
+
stats = {
|
|
333
|
+
action.id: {
|
|
334
|
+
"attempts": 0,
|
|
335
|
+
"correctness_evaluated": 0,
|
|
336
|
+
"correct": 0,
|
|
337
|
+
"correctness_rate": None,
|
|
338
|
+
"rewarded_episodes": 0,
|
|
339
|
+
"mean_reward": None,
|
|
340
|
+
"recent_outcomes": [],
|
|
341
|
+
}
|
|
342
|
+
for action in snapshot.actions
|
|
343
|
+
}
|
|
344
|
+
reward_totals = {action.id: 0.0 for action in snapshot.actions}
|
|
345
|
+
episodes = store.query_episodes(
|
|
346
|
+
snapshot.agent_id,
|
|
347
|
+
task_id=snapshot.task_id,
|
|
348
|
+
limit=history_limit,
|
|
349
|
+
)
|
|
350
|
+
for episode in episodes:
|
|
351
|
+
action_id = episode.action_id
|
|
352
|
+
if action_id not in stats:
|
|
353
|
+
continue
|
|
354
|
+
action_stats = stats[action_id]
|
|
355
|
+
action_stats["attempts"] += 1
|
|
356
|
+
correct_action_id = episode.metadata.get("correct_action_id")
|
|
357
|
+
was_correct = None
|
|
358
|
+
if correct_action_id:
|
|
359
|
+
was_correct = action_id == correct_action_id
|
|
360
|
+
action_stats["correctness_evaluated"] += 1
|
|
361
|
+
action_stats["correct"] += int(was_correct)
|
|
362
|
+
reward = _latest_aggregate(store, episode)
|
|
363
|
+
if reward is not None:
|
|
364
|
+
action_stats["rewarded_episodes"] += 1
|
|
365
|
+
reward_totals[action_id] += reward
|
|
366
|
+
if len(action_stats["recent_outcomes"]) < 3:
|
|
367
|
+
score_breakdown = {}
|
|
368
|
+
for result in store.get_metric_results(episode.id, episode.agent_id):
|
|
369
|
+
score_breakdown[result.metric.value] = {
|
|
370
|
+
"normalized": result.normalized,
|
|
371
|
+
"status": result.status,
|
|
372
|
+
"reason": result.reason,
|
|
373
|
+
}
|
|
374
|
+
action_stats["recent_outcomes"].append(
|
|
375
|
+
{
|
|
376
|
+
"created_at": episode.created_at,
|
|
377
|
+
"was_correct": was_correct,
|
|
378
|
+
"reward": reward,
|
|
379
|
+
"execution_status": episode.execution_status,
|
|
380
|
+
"result_summary": episode.result_summary,
|
|
381
|
+
"score_breakdown": score_breakdown,
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
for action_id, action_stats in stats.items():
|
|
385
|
+
evaluated = action_stats["correctness_evaluated"]
|
|
386
|
+
rewarded = action_stats["rewarded_episodes"]
|
|
387
|
+
if evaluated:
|
|
388
|
+
action_stats["correctness_rate"] = action_stats["correct"] / evaluated
|
|
389
|
+
if rewarded:
|
|
390
|
+
action_stats["mean_reward"] = reward_totals[action_id] / rewarded
|
|
391
|
+
return {"episodes_reviewed": len(episodes), "actions": stats}
|
|
392
|
+
|
|
393
|
+
|
|
269
394
|
def _policy_difference(
|
|
270
395
|
current: PolicySnapshot, previous: PolicySnapshot | None
|
|
271
396
|
) -> dict[str, Any] | None:
|
|
@@ -326,6 +451,78 @@ def _cmd_show_task_policy(args: argparse.Namespace) -> int:
|
|
|
326
451
|
return 0
|
|
327
452
|
|
|
328
453
|
|
|
454
|
+
def _cmd_decide_task_policy(args: argparse.Namespace) -> int:
|
|
455
|
+
store = get_default_store()
|
|
456
|
+
snapshot = store.get_active_policy(args.agent_id, args.task_id)
|
|
457
|
+
if snapshot is None:
|
|
458
|
+
print(
|
|
459
|
+
f"No active policy found for agent_id={args.agent_id!r}, "
|
|
460
|
+
f"task_id={args.task_id!r}.",
|
|
461
|
+
file=sys.stderr,
|
|
462
|
+
)
|
|
463
|
+
return 2
|
|
464
|
+
if not _is_decision_policy(snapshot):
|
|
465
|
+
print(
|
|
466
|
+
"The active policy is not marked as a delegated decision policy. "
|
|
467
|
+
"Questions, reporting tasks, and agent-learning automation are not eligible.",
|
|
468
|
+
file=sys.stderr,
|
|
469
|
+
)
|
|
470
|
+
return 2
|
|
471
|
+
rng = random.Random(args.seed) if args.seed is not None else None
|
|
472
|
+
policy = SoftmaxPolicy.from_snapshot(snapshot, rng=rng)
|
|
473
|
+
probabilities = policy.probabilities()
|
|
474
|
+
recommended_index = max(range(len(probabilities)), key=probabilities.__getitem__)
|
|
475
|
+
if args.greedy:
|
|
476
|
+
selected_index = recommended_index
|
|
477
|
+
selected_action = snapshot.actions[selected_index]
|
|
478
|
+
selected_probability = probabilities[selected_index]
|
|
479
|
+
logprob = math.log(max(selected_probability, 1e-12))
|
|
480
|
+
mode = "greedy"
|
|
481
|
+
else:
|
|
482
|
+
decision = policy.choose()
|
|
483
|
+
selected_action = decision.action
|
|
484
|
+
selected_index = next(
|
|
485
|
+
index
|
|
486
|
+
for index, action in enumerate(snapshot.actions)
|
|
487
|
+
if action.id == selected_action.id
|
|
488
|
+
)
|
|
489
|
+
selected_probability = probabilities[selected_index]
|
|
490
|
+
logprob = decision.logprob
|
|
491
|
+
mode = "sampled"
|
|
492
|
+
feedback = _decision_feedback(store, snapshot, args.history_limit)
|
|
493
|
+
selected_stats = feedback["actions"][selected_action.id]
|
|
494
|
+
recommendation = snapshot.actions[recommended_index]
|
|
495
|
+
print(
|
|
496
|
+
json.dumps(
|
|
497
|
+
{
|
|
498
|
+
"agent_id": snapshot.agent_id,
|
|
499
|
+
"task_id": snapshot.task_id,
|
|
500
|
+
"decision_context": snapshot.metadata.get("decision_context"),
|
|
501
|
+
"policy_id": snapshot.id,
|
|
502
|
+
"policy_version": snapshot.version,
|
|
503
|
+
"selection_mode": mode,
|
|
504
|
+
"selected_action": {
|
|
505
|
+
**selected_action.to_dict(),
|
|
506
|
+
"probability": selected_probability,
|
|
507
|
+
"logprob": logprob,
|
|
508
|
+
},
|
|
509
|
+
"recommended_action": {
|
|
510
|
+
**recommendation.to_dict(),
|
|
511
|
+
"probability": probabilities[recommended_index],
|
|
512
|
+
},
|
|
513
|
+
"action_probabilities": {
|
|
514
|
+
action.id: probability
|
|
515
|
+
for action, probability in zip(snapshot.actions, probabilities)
|
|
516
|
+
},
|
|
517
|
+
"selected_action_feedback": selected_stats,
|
|
518
|
+
"historical_feedback": feedback,
|
|
519
|
+
},
|
|
520
|
+
indent=2,
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
return 0
|
|
524
|
+
|
|
525
|
+
|
|
329
526
|
def _cmd_init_task_policy(args: argparse.Namespace) -> int:
|
|
330
527
|
store = get_default_store()
|
|
331
528
|
if store.get_active_policy(args.agent_id, args.task_id) is not None:
|
|
@@ -341,20 +538,35 @@ def _cmd_init_task_policy(args: argparse.Namespace) -> int:
|
|
|
341
538
|
except (OSError, json.JSONDecodeError) as exc:
|
|
342
539
|
print(f"Unable to read --actions file: {exc}", file=sys.stderr)
|
|
343
540
|
return 2
|
|
344
|
-
if not isinstance(action_payloads, list) or
|
|
345
|
-
print(
|
|
541
|
+
if not isinstance(action_payloads, list) or len(action_payloads) < 2:
|
|
542
|
+
print(
|
|
543
|
+
"--actions file must contain at least two delegated decision actions",
|
|
544
|
+
file=sys.stderr,
|
|
545
|
+
)
|
|
346
546
|
return 2
|
|
347
547
|
try:
|
|
348
548
|
actions = [Action.from_dict(item) for item in action_payloads]
|
|
349
549
|
except (KeyError, TypeError, ValueError) as exc:
|
|
350
550
|
print(f"Invalid action definition: {exc}", file=sys.stderr)
|
|
351
551
|
return 2
|
|
552
|
+
action_ids = [action.id for action in actions]
|
|
553
|
+
if any(not action_id.strip() for action_id in action_ids) or len(set(action_ids)) != len(
|
|
554
|
+
action_ids
|
|
555
|
+
):
|
|
556
|
+
print("Decision action ids must be non-empty and unique", file=sys.stderr)
|
|
557
|
+
return 2
|
|
352
558
|
policy = SoftmaxPolicy.from_actions(
|
|
353
559
|
actions,
|
|
354
560
|
agent_id=args.agent_id,
|
|
355
561
|
task_id=args.task_id,
|
|
356
562
|
)
|
|
357
563
|
snapshot = policy.snapshot()
|
|
564
|
+
snapshot.metadata.update(
|
|
565
|
+
{
|
|
566
|
+
"policy_scope": _DECISION_POLICY_SCOPE,
|
|
567
|
+
"decision_context": args.decision_context,
|
|
568
|
+
}
|
|
569
|
+
)
|
|
358
570
|
store.store_policy(snapshot)
|
|
359
571
|
print(json.dumps(_policy_payload(snapshot), indent=2))
|
|
360
572
|
return 0
|
|
@@ -389,6 +601,26 @@ def _cmd_register_task_episode(args: argparse.Namespace) -> int:
|
|
|
389
601
|
print(f"Invalid episode definition: {exc}", file=sys.stderr)
|
|
390
602
|
return 2
|
|
391
603
|
|
|
604
|
+
if args.require_decision_policy:
|
|
605
|
+
policy = get_default_store().get_policy(episode.policy_id or "", args.agent_id)
|
|
606
|
+
if not _is_decision_policy(policy) or policy.task_id != args.task_id:
|
|
607
|
+
print(
|
|
608
|
+
"Episode registration requires a delegated decision policy and its policy_id.",
|
|
609
|
+
file=sys.stderr,
|
|
610
|
+
)
|
|
611
|
+
return 2
|
|
612
|
+
action_ids = {action.id for action in policy.actions}
|
|
613
|
+
if episode.action_id not in action_ids:
|
|
614
|
+
print("Episode action_id is not in the delegated decision policy.", file=sys.stderr)
|
|
615
|
+
return 2
|
|
616
|
+
correct_action_id = episode.metadata.get("correct_action_id")
|
|
617
|
+
if correct_action_id is not None and correct_action_id not in action_ids:
|
|
618
|
+
print(
|
|
619
|
+
"Episode metadata.correct_action_id is not in the delegated decision policy.",
|
|
620
|
+
file=sys.stderr,
|
|
621
|
+
)
|
|
622
|
+
return 2
|
|
623
|
+
|
|
392
624
|
get_default_store().store_episode(episode)
|
|
393
625
|
print(json.dumps(episode.to_dict(), indent=2))
|
|
394
626
|
return 0
|
|
@@ -406,6 +638,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
406
638
|
"train": _cmd_train,
|
|
407
639
|
"score": _cmd_score,
|
|
408
640
|
"task-policy": _cmd_show_task_policy,
|
|
641
|
+
"task-policy-decide": _cmd_decide_task_policy,
|
|
409
642
|
"task-policy-init": _cmd_init_task_policy,
|
|
410
643
|
"task-episode-register": _cmd_register_task_episode,
|
|
411
644
|
}
|
|
@@ -56,8 +56,9 @@ class LearningStore(ABC):
|
|
|
56
56
|
end_date: Optional[str] = None,
|
|
57
57
|
policy_id: Optional[str] = None,
|
|
58
58
|
task_id: Optional[str] = None,
|
|
59
|
+
full_only: bool = False,
|
|
59
60
|
) -> List[Episode]:
|
|
60
|
-
"""List episodes filtered by
|
|
61
|
+
"""List episodes filtered by time, policy, task, or completeness."""
|
|
61
62
|
|
|
62
63
|
@abstractmethod
|
|
63
64
|
def count_episodes(
|