@statelyai/agent 2.0.0-next.3 → 2.0.0-next.5
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/.changeset/calm-beans-talk.md +5 -0
- package/.changeset/long-guests-explode.md +5 -0
- package/.changeset/nice-pants-rule.md +10 -0
- package/.changeset/odd-kiwis-compare.md +5 -0
- package/.changeset/pre.json +4 -0
- package/CHANGELOG.md +23 -0
- package/architecture.tldr +797 -0
- package/dist/index.d.mts +198 -140
- package/dist/index.d.ts +198 -140
- package/dist/index.js +4397 -148
- package/dist/index.mjs +4397 -149
- package/examples/chatbot.ts +9 -5
- package/examples/cot.ts +2 -4
- package/examples/jugs.ts +2 -2
- package/examples/learn-from-feedback.ts +7 -7
- package/examples/newspaper.ts +1 -1
- package/examples/rewoo.ts +62 -0
- package/examples/river-crossing.ts +2 -2
- package/examples/serverless.ts +71 -0
- package/examples/simple.ts +1 -1
- package/examples/ticTacToe.ts +6 -2
- package/examples/wiki.ts +2 -2
- package/package.json +14 -12
- package/readme.md +57 -0
- package/src/agent.test.ts +387 -30
- package/src/agent.ts +177 -64
- package/src/decide.test.ts +24 -2
- package/src/decide.ts +34 -78
- package/src/index.ts +1 -0
- package/src/{strategies/chainOfThought.ts → policies/chainOfThoughtPolicy.ts} +7 -9
- package/src/policies/index.ts +3 -0
- package/src/{strategies/shortestPath.test.ts → policies/shortestPathPolicy.test.ts} +2 -2
- package/src/{strategies/shortestPath.ts → policies/shortestPathPolicy.ts} +8 -8
- package/src/{strategies/simple.ts → policies/toolPolicy.ts} +27 -26
- package/src/text.ts +17 -22
- package/src/types.ts +162 -166
- package/src/agent-experimental.ts +0 -221
package/examples/chatbot.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { createAgent, fromDecision, TypesFromAgent } from '../src';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { assign, createActor, log, setup } from 'xstate';
|
|
5
|
-
import { fromTerminal } from './helpers/helpers';
|
|
5
|
+
import { fromTerminal, getFromTerminal } from './helpers/helpers';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
id: 'chatbot',
|
|
@@ -60,7 +60,11 @@ const machine = setup({
|
|
|
60
60
|
|
|
61
61
|
const actor = createActor(machine).start();
|
|
62
62
|
|
|
63
|
-
agent.interact(actor, () =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
agent.interact(actor, (s) => {
|
|
64
|
+
if (s.state.matches('responding')) {
|
|
65
|
+
return {
|
|
66
|
+
goal: 'Respond to the user, unless they want to end the conversation.',
|
|
67
|
+
messages: agent.getMessages(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
package/examples/cot.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { createAgent } from '../src';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { getFromTerminal } from './helpers/helpers';
|
|
5
|
-
import {
|
|
5
|
+
import { chainOfThoughtPolicy } from '../src/policies/chainOfThoughtPolicy';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
id: 'chain-of-thought',
|
|
@@ -15,11 +15,9 @@ const agent = createAgent({
|
|
|
15
15
|
context: {
|
|
16
16
|
question: z.string().nullable(),
|
|
17
17
|
},
|
|
18
|
-
|
|
18
|
+
policy: chainOfThoughtPolicy,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
// agent.onMessage((msg) => console.log(msg.content));
|
|
22
|
-
|
|
23
21
|
async function main() {
|
|
24
22
|
const msg = await getFromTerminal('what?');
|
|
25
23
|
|
package/examples/jugs.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createAgent, TypesFromAgent } from '../src';
|
|
|
2
2
|
import { assign, createActor, setup } from 'xstate';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
5
|
+
import { experimental_shortestPathPolicy } from '../src/policies/shortestPathPolicy';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
id: 'die-hard-solver',
|
|
@@ -103,7 +103,7 @@ async function main() {
|
|
|
103
103
|
machine: waterJugMachine,
|
|
104
104
|
goal: 'Get exactly 4 gallons of water in the 5-gallon jug',
|
|
105
105
|
state: waterJugActor.getSnapshot(),
|
|
106
|
-
|
|
106
|
+
policy: experimental_shortestPathPolicy,
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
console.log(decision?.nextEvent);
|
|
@@ -35,7 +35,7 @@ async function main() {
|
|
|
35
35
|
const relevantFeedback = await agent
|
|
36
36
|
.getFeedback()
|
|
37
37
|
.filter((f) =>
|
|
38
|
-
relevantObservations.find((o) => o.
|
|
38
|
+
relevantObservations.find((o) => o.decisionId === f.decisionId)
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
const decision = await agent.decide({
|
|
@@ -79,24 +79,24 @@ Achieve the goal. Consider both exploring unknown actions (high exploration_valu
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
if (decision?.nextEvent?.type === 'submit') {
|
|
82
|
-
const observation =
|
|
83
|
-
|
|
82
|
+
const observation = agent.addObservation({
|
|
83
|
+
decisionId: decision.id,
|
|
84
84
|
prevState: { value: 'editing' },
|
|
85
85
|
event: { type: 'submit' },
|
|
86
86
|
state: { value: 'editing' },
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// don't change the status; pretend submit button is broken
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
agent.addFeedback({
|
|
91
|
+
decisionId: observation.id,
|
|
92
|
+
reward: 0,
|
|
93
93
|
comment: 'Form not submitted',
|
|
94
94
|
});
|
|
95
95
|
} else if (decision?.nextEvent?.type === 'pressEnter') {
|
|
96
96
|
status = 'submitted';
|
|
97
97
|
|
|
98
98
|
await agent.addObservation({
|
|
99
|
-
|
|
99
|
+
decisionId: decision.id,
|
|
100
100
|
prevState: { value: 'editing' },
|
|
101
101
|
event: { type: 'pressEnter' },
|
|
102
102
|
state: { value: 'submitted' },
|
package/examples/newspaper.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { assign, createActor, fromPromise, setup } from 'xstate';
|
|
|
8
8
|
import { createAgent } from '../src';
|
|
9
9
|
import { openai } from '@ai-sdk/openai';
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { generateObject
|
|
11
|
+
import { generateObject } from 'ai';
|
|
12
12
|
|
|
13
13
|
interface AgentState {
|
|
14
14
|
topic: string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { setup } from 'xstate';
|
|
2
|
+
|
|
3
|
+
interface RewooContext {
|
|
4
|
+
task: string;
|
|
5
|
+
planString: string;
|
|
6
|
+
steps: string[][];
|
|
7
|
+
results: Record<string, any>;
|
|
8
|
+
result: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const createTemplate = (input: { task: string }) =>
|
|
12
|
+
`For the following task, make plans that can solve the problem step by step. For each plan, indicate
|
|
13
|
+
which external tool together with tool input to retrieve evidence. You can store the evidence into a
|
|
14
|
+
variable #E that can be called by later tools. (Plan, #E1, Plan, #E2, Plan, ...)
|
|
15
|
+
|
|
16
|
+
Tools can be one of the following:
|
|
17
|
+
(1) Google[input]: Worker that searches results from Google. Useful when you need to find short
|
|
18
|
+
and succinct answers about a specific topic. The input should be a search query.
|
|
19
|
+
(2) LLM[input]: A pre-trained LLM like yourself. Useful when you need to act with general
|
|
20
|
+
world knowledge and common sense. Prioritize it when you are confident in solving the problem
|
|
21
|
+
yourself. Input can be any instruction.
|
|
22
|
+
|
|
23
|
+
For example,
|
|
24
|
+
Task: Thomas, Toby, and Rebecca worked a total of 157 hours in one week. Thomas worked x
|
|
25
|
+
hours. Toby worked 10 hours less than twice what Thomas worked, and Rebecca worked 8 hours
|
|
26
|
+
less than Toby. How many hours did Rebecca work?
|
|
27
|
+
Plan: Given Thomas worked x hours, translate the problem into algebraic expressions and solve with Wolfram Alpha.
|
|
28
|
+
#E1 = WolframAlpha[Solve x + (2x - 10) + ((2x - 10) - 8) = 157]
|
|
29
|
+
Plan: Find out the number of hours Thomas worked.
|
|
30
|
+
#E2 = LLM[What is x, given #E1]
|
|
31
|
+
Plan: Calculate the number of hours Rebecca worked.
|
|
32
|
+
#E3 = Calculator[(2 * #E2 - 10) - 8]
|
|
33
|
+
|
|
34
|
+
Important!
|
|
35
|
+
Variables/results MUST be referenced using the # symbol!
|
|
36
|
+
The plan will be executed as a program, so no coreference resolution apart from naive variable replacement is allowed.
|
|
37
|
+
The ONLY way for steps to share context is by including #E<step> within the arguments of the tool.
|
|
38
|
+
|
|
39
|
+
Begin!
|
|
40
|
+
Describe your plans with rich details. Each Plan should be followed by only one #E.
|
|
41
|
+
|
|
42
|
+
Task: ${input.task}`;
|
|
43
|
+
|
|
44
|
+
const machine = setup({
|
|
45
|
+
types: {
|
|
46
|
+
context: {} as RewooContext,
|
|
47
|
+
},
|
|
48
|
+
}).createMachine({
|
|
49
|
+
context: {
|
|
50
|
+
planString: '',
|
|
51
|
+
result: '',
|
|
52
|
+
steps: [],
|
|
53
|
+
task: '',
|
|
54
|
+
results: [],
|
|
55
|
+
},
|
|
56
|
+
initial: 'plan',
|
|
57
|
+
states: {
|
|
58
|
+
plan: {},
|
|
59
|
+
tool: {},
|
|
60
|
+
solve: {},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -2,7 +2,7 @@ import { createAgent, TypesFromAgent } from '../src';
|
|
|
2
2
|
import { assign, createActor, setup } from 'xstate';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
5
|
+
import { experimental_shortestPathPolicy } from '../src/policies/shortestPathPolicy';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
id: 'river-crossing-solver',
|
|
@@ -118,7 +118,7 @@ async function main() {
|
|
|
118
118
|
machine: riverCrossingMachine,
|
|
119
119
|
goal: 'Get all items safely across the river. Remember: Cannot leave wolf with goat or goat with cabbage unattended.',
|
|
120
120
|
state: riverActor.getSnapshot(),
|
|
121
|
-
|
|
121
|
+
policy: experimental_shortestPathPolicy,
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
console.log(decision?.nextEvent);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { openai } from '@ai-sdk/openai';
|
|
2
|
+
import {
|
|
3
|
+
AgentDecision,
|
|
4
|
+
AgentFeedback,
|
|
5
|
+
AgentInsight,
|
|
6
|
+
AgentMessage,
|
|
7
|
+
AgentObservation,
|
|
8
|
+
createAgent,
|
|
9
|
+
} from '../src';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
const agent = createAgent({
|
|
13
|
+
id: 'simple',
|
|
14
|
+
model: openai('gpt-4o-mini'),
|
|
15
|
+
events: {
|
|
16
|
+
'agent.moveLeft': z.object({}),
|
|
17
|
+
'agent.moveRight': z.object({}),
|
|
18
|
+
'agent.doNothing': z.object({}),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const db = {
|
|
23
|
+
observations: [] as AgentObservation<any>[],
|
|
24
|
+
feedbackItems: [] as AgentFeedback[],
|
|
25
|
+
decisions: [] as AgentDecision[],
|
|
26
|
+
messages: [] as AgentMessage[],
|
|
27
|
+
insights: [] as AgentInsight[],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// async function postObservation(req: unknown) {
|
|
31
|
+
// db.observations.push(req.body);
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
async function getDecision(req: {
|
|
35
|
+
query: {
|
|
36
|
+
episodeId: string;
|
|
37
|
+
goal: string;
|
|
38
|
+
};
|
|
39
|
+
}) {
|
|
40
|
+
// Get relevant observations
|
|
41
|
+
const observations = db.observations
|
|
42
|
+
.filter((obs) => obs.episodeId === req.query.episodeId)
|
|
43
|
+
.at(-1);
|
|
44
|
+
const similarObservations = db.observations.filter(
|
|
45
|
+
(obs) => obs.prevState?.value === observations?.prevState?.value
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Get relevant feedback
|
|
49
|
+
const similarFeedback = db.feedbackItems.filter((fb) => {
|
|
50
|
+
similarObservations.map((obs) => obs.decisionId).includes(fb.decisionId);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Get relevant insights
|
|
54
|
+
const insights = db.insights.filter((insight) =>
|
|
55
|
+
similarObservations.map((obs) => obs.id).includes(insight.observationId)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const decision = await agent.decide({
|
|
59
|
+
goal: req.query.goal,
|
|
60
|
+
state: observations?.state,
|
|
61
|
+
observations: similarObservations,
|
|
62
|
+
feedback: similarFeedback,
|
|
63
|
+
insights,
|
|
64
|
+
allowedEvents: ['agent.moveLeft', 'agent.moveRight'],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
db.decisions.push(...agent.getDecisions());
|
|
68
|
+
db.messages.push(...agent.getMessages());
|
|
69
|
+
|
|
70
|
+
return decision;
|
|
71
|
+
}
|
package/examples/simple.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createAgent, fromDecision } from '../src';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { setup, createActor, createMachine } from 'xstate';
|
|
4
4
|
import { openai } from '@ai-sdk/openai';
|
|
5
|
-
import {
|
|
5
|
+
import { chainOfThoughtPolicy } from '../src/policies/chainOfThoughtPolicy';
|
|
6
6
|
|
|
7
7
|
const agent = createAgent({
|
|
8
8
|
id: 'simple',
|
package/examples/ticTacToe.ts
CHANGED
|
@@ -267,8 +267,12 @@ xAgent.interact(actor, (observed) => {
|
|
|
267
267
|
|
|
268
268
|
console.log('Similar:', similarObservations);
|
|
269
269
|
|
|
270
|
-
const similarFeedbacks = similarObservations.map((
|
|
271
|
-
return xAgent
|
|
270
|
+
const similarFeedbacks = similarObservations.map((observation) => {
|
|
271
|
+
return xAgent
|
|
272
|
+
.getFeedback()
|
|
273
|
+
.filter(
|
|
274
|
+
(feedbackItem) => feedbackItem.decisionId === observation.decisionId
|
|
275
|
+
);
|
|
272
276
|
});
|
|
273
277
|
|
|
274
278
|
console.log('Feedbacks:', similarFeedbacks);
|
package/examples/wiki.ts
CHANGED
|
@@ -21,12 +21,12 @@ agent.onMessage((msg) => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
async function main() {
|
|
24
|
-
const
|
|
24
|
+
const result = await generateText({
|
|
25
25
|
model: agent.model,
|
|
26
26
|
prompt: 'When was Deadpool 2 released?',
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
for (const msg of await response.
|
|
29
|
+
for (const msg of await result.response.messages) {
|
|
30
30
|
agent.addMessage(msg);
|
|
31
31
|
}
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.5",
|
|
4
4
|
"description": "Stateful agents that make decisions based on finite-state machine models",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -18,33 +18,35 @@
|
|
|
18
18
|
"@ai-sdk/anthropic": "^0.0.54",
|
|
19
19
|
"@ai-sdk/openai": "^0.0.40",
|
|
20
20
|
"@changesets/changelog-github": "^0.5.0",
|
|
21
|
-
"@changesets/cli": "^2.27.
|
|
21
|
+
"@changesets/cli": "^2.27.12",
|
|
22
22
|
"@langchain/community": "^0.0.53",
|
|
23
23
|
"@langchain/core": "^0.1.63",
|
|
24
24
|
"@langchain/openai": "^0.0.28",
|
|
25
25
|
"@tavily/core": "^0.0.2",
|
|
26
|
-
"@types/node": "^20.17.
|
|
26
|
+
"@types/node": "^20.17.17",
|
|
27
27
|
"@types/object-hash": "^3.0.6",
|
|
28
|
-
"@vitest/coverage-v8": "^2.1.
|
|
29
|
-
"dotenv": "^16.4.
|
|
28
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
29
|
+
"dotenv": "^16.4.7",
|
|
30
30
|
"json-schema-to-ts": "^3.1.1",
|
|
31
31
|
"ts-node": "^10.9.2",
|
|
32
|
-
"tsup": "^8.3.
|
|
33
|
-
"typescript": "^5.
|
|
34
|
-
"vitest": "^2.1.
|
|
32
|
+
"tsup": "^8.3.6",
|
|
33
|
+
"typescript": "^5.7.3",
|
|
34
|
+
"vitest": "^2.1.9",
|
|
35
35
|
"wikipedia": "^2.1.2",
|
|
36
|
-
"zod": "^3.
|
|
36
|
+
"zod": "^3.24.2"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@xstate/graph": "^2.0.1",
|
|
43
|
-
"ai": "^3.4.31",
|
|
44
43
|
"ajv": "^8.17.1",
|
|
45
44
|
"object-hash": "^3.0.0",
|
|
46
|
-
"xstate": "^5.
|
|
47
|
-
"zod-to-json-schema": "^3.
|
|
45
|
+
"xstate": "^5.19.2",
|
|
46
|
+
"zod-to-json-schema": "^3.24.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"ai": "^4.1.36"
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|
|
50
52
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
package/readme.md
CHANGED
|
@@ -8,3 +8,60 @@ Stately Agent is a flexible framework for building AI agents using state machine
|
|
|
8
8
|
- First-class integration with the [Vercel AI SDK](https://sdk.vercel.ai/) to easily support multiple model providers, such as OpenAI, Anthropic, Google, Mistral, Groq, Perplexity, and more
|
|
9
9
|
|
|
10
10
|
**Read the documentation: [stately.ai/docs/agents](https://stately.ai/docs/agents)**
|
|
11
|
+
|
|
12
|
+
# Stately Agent
|
|
13
|
+
|
|
14
|
+
Stately Agent is a framework for building intelligent AI agents that are guided by state machines and learn from experience. Rather than relying solely on LLM responses, agents use structured observations, feedback, and insights to make informed decisions and improve over time.
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
Stately Agent combines state machines with reinforcement learning concepts to create agents that:
|
|
19
|
+
|
|
20
|
+
- Make decisions based on clear state transitions and goals
|
|
21
|
+
- Learn from past experiences and feedback
|
|
22
|
+
- Generate insights about state changes
|
|
23
|
+
- Improve decision-making through structured rewards
|
|
24
|
+
- Support multiple decision-making policies
|
|
25
|
+
|
|
26
|
+
The framework is built on [XState](https://stately.ai/docs/xstate) for state machine management and integrates with the [Vercel AI SDK](https://sdk.vercel.ai/) for flexible LLM support.
|
|
27
|
+
|
|
28
|
+
## Key Concepts
|
|
29
|
+
|
|
30
|
+
- **Observations**: Records of state transitions, containing:
|
|
31
|
+
|
|
32
|
+
- Previous state
|
|
33
|
+
- Event/action taken
|
|
34
|
+
- Resulting state
|
|
35
|
+
- Metadata about the transition
|
|
36
|
+
|
|
37
|
+
- **Decisions**: Actions the agent chooses to take based on:
|
|
38
|
+
|
|
39
|
+
- Current state
|
|
40
|
+
- Goal state
|
|
41
|
+
- Past observations
|
|
42
|
+
- Available feedback and insights
|
|
43
|
+
- Decision-making policy
|
|
44
|
+
|
|
45
|
+
- **Feedback**: Rewards or evaluations given to decisions, helping the agent learn which actions are effective
|
|
46
|
+
|
|
47
|
+
- **Insights**: Additional context about state transitions, helping the agent understand cause and effect
|
|
48
|
+
|
|
49
|
+
- **Episodes**: Complete sequences of state transitions, from initial state to goal state (similar to RL episodes)
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
TODO
|
|
54
|
+
|
|
55
|
+
## Why Stately Agent?
|
|
56
|
+
|
|
57
|
+
Traditional LLM-based agents often make decisions with limited context and no ability to learn from experience. Stately Agent provides:
|
|
58
|
+
|
|
59
|
+
1. **Structured Decision Making**: State machines provide clear boundaries and valid transitions
|
|
60
|
+
|
|
61
|
+
2. **Learning from Experience**: Agents improve through feedback and observations
|
|
62
|
+
|
|
63
|
+
3. **Contextual Awareness**: Insights and observations inform better decisions
|
|
64
|
+
|
|
65
|
+
4. **Flexible Policies**: Different approaches for different needs
|
|
66
|
+
|
|
67
|
+
5. **Storage Integration**: Optional persistence of experiences and learning
|