Skip to content

Commit fe8cd4d

Browse files
gusye1234GenerQAQ
andauthored
feat(core, api, sdk): add user confirmation queue (#42) (#45)
* feat(core, api): add experience confirmation table * fix(core): add task_id for experience confirm * feat(api): add list and confirm experience api * feat(sdk): add user confirmation sdk * chore: api build docs * feat(core): set digested for unconfirm sops * fix: confirm save body and sdk * docs: add wait-user * refactor(api): rename unconfirmed experiences endpoint to experience confirmations * docs: add example data --------- Co-authored-by: Gener <435669237@qq.com>
1 parent d36b904 commit fe8cd4d

40 files changed

Lines changed: 2619 additions & 21 deletions

File tree

docs/api-reference/openapi.json

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,130 @@
16281628
"x-codegen-request-body-name" : "payload"
16291629
}
16301630
},
1631+
"/space/{space_id}/experience_confirmations" : {
1632+
"get" : {
1633+
"description" : "Get all experience confirmations in a space with cursor-based pagination",
1634+
"parameters" : [ {
1635+
"description" : "Space ID",
1636+
"in" : "path",
1637+
"name" : "space_id",
1638+
"required" : true,
1639+
"schema" : {
1640+
"format" : "uuid",
1641+
"type" : "string"
1642+
}
1643+
}, {
1644+
"description" : "Limit of confirmations to return, default 20. Max 200.",
1645+
"in" : "query",
1646+
"name" : "limit",
1647+
"schema" : {
1648+
"type" : "integer"
1649+
}
1650+
}, {
1651+
"description" : "Cursor for pagination. Use the cursor from the previous response to get the next page.",
1652+
"in" : "query",
1653+
"name" : "cursor",
1654+
"schema" : {
1655+
"type" : "string"
1656+
}
1657+
}, {
1658+
"description" : "Order by created_at descending if true, ascending if false (default false)",
1659+
"in" : "query",
1660+
"name" : "time_desc",
1661+
"schema" : {
1662+
"type" : "boolean"
1663+
}
1664+
} ],
1665+
"responses" : {
1666+
"200" : {
1667+
"content" : {
1668+
"application/json" : {
1669+
"schema" : {
1670+
"$ref" : "#/components/schemas/_space__space_id__experience_confirmations_get_200_response"
1671+
}
1672+
}
1673+
},
1674+
"description" : "OK"
1675+
}
1676+
},
1677+
"security" : [ {
1678+
"BearerAuth" : [ ]
1679+
} ],
1680+
"summary" : "Get experience confirmations",
1681+
"tags" : [ "space" ],
1682+
"x-code-samples" : [ {
1683+
"label" : "Python",
1684+
"lang" : "python",
1685+
"source" : "from acontext import AcontextClient\n\nclient = AcontextClient(api_key='sk_project_token')\n\n# Get unconfirmed experiences\nexperiences = client.spaces.get_unconfirmed_experiences(\n space_id='space-uuid',\n limit=20,\n time_desc=True\n)\nfor experience in experiences.items:\n print(f\"{experience.id}: {experience.experience_data}\")\n\n# If there are more, use the cursor for pagination\nif experiences.has_more:\n next_experiences = client.spaces.get_unconfirmed_experiences(\n space_id='space-uuid',\n limit=20,\n cursor=experiences.next_cursor\n )\n"
1686+
}, {
1687+
"label" : "JavaScript",
1688+
"lang" : "javascript",
1689+
"source" : "import { AcontextClient } from '@acontext/acontext';\n\nconst client = new AcontextClient({ apiKey: 'sk_project_token' });\n\n// Get unconfirmed experiences\nconst experiences = await client.spaces.getUnconfirmedExperiences('space-uuid', {\n limit: 20,\n timeDesc: true\n});\nfor (const experience of experiences.items) {\n console.log(`${experience.id}: ${JSON.stringify(experience.experience_data)}`);\n}\n\n// If there are more, use the cursor for pagination\nif (experiences.hasMore) {\n const nextExperiences = await client.spaces.getUnconfirmedExperiences('space-uuid', {\n limit: 20,\n cursor: experiences.nextCursor\n });\n}\n"
1690+
} ]
1691+
}
1692+
},
1693+
"/space/{space_id}/experience_confirmations/{experience_id}" : {
1694+
"patch" : {
1695+
"description" : "Confirm an experience confirmation. If save is false, delete the row. If save is true, get the data first (TODO: process data), then delete the row.",
1696+
"parameters" : [ {
1697+
"description" : "Space ID",
1698+
"in" : "path",
1699+
"name" : "space_id",
1700+
"required" : true,
1701+
"schema" : {
1702+
"format" : "uuid",
1703+
"type" : "string"
1704+
}
1705+
}, {
1706+
"description" : "Experience Confirmation ID",
1707+
"in" : "path",
1708+
"name" : "experience_id",
1709+
"required" : true,
1710+
"schema" : {
1711+
"format" : "uuid",
1712+
"type" : "string"
1713+
}
1714+
} ],
1715+
"requestBody" : {
1716+
"content" : {
1717+
"application/json" : {
1718+
"schema" : {
1719+
"$ref" : "#/components/schemas/handler.ConfirmExperienceReq"
1720+
}
1721+
}
1722+
},
1723+
"description" : "Confirmation request with save flag",
1724+
"required" : true
1725+
},
1726+
"responses" : {
1727+
"200" : {
1728+
"content" : {
1729+
"application/json" : {
1730+
"schema" : {
1731+
"$ref" : "#/components/schemas/_space__space_id__experience_confirmations__experience_id__patch_200_response"
1732+
}
1733+
}
1734+
},
1735+
"description" : "OK"
1736+
}
1737+
},
1738+
"security" : [ {
1739+
"BearerAuth" : [ ]
1740+
} ],
1741+
"summary" : "Confirm experience",
1742+
"tags" : [ "space" ],
1743+
"x-code-samples" : [ {
1744+
"label" : "Python",
1745+
"lang" : "python",
1746+
"source" : "from acontext import AcontextClient\n\nclient = AcontextClient(api_key='sk_project_token')\n\n# Confirm experience and save data\nconfirmation = client.spaces.confirm_experience(\n space_id='space-uuid',\n experience_id='experience-uuid',\n save=True\n)\nprint(f\"Saved confirmation: {confirmation.experience_data}\")\n\n# Confirm experience without saving (just delete)\nclient.spaces.confirm_experience(\n space_id='space-uuid',\n experience_id='experience-uuid',\n save=False\n)\n"
1747+
}, {
1748+
"label" : "JavaScript",
1749+
"lang" : "javascript",
1750+
"source" : "import { AcontextClient } from '@acontext/acontext';\n\nconst client = new AcontextClient({ apiKey: 'sk_project_token' });\n\n// Confirm experience and save data\nconst confirmation = await client.spaces.confirmExperience('space-uuid', 'experience-uuid', {\n save: true\n});\nconsole.log(`Saved confirmation: ${JSON.stringify(confirmation.experience_data)}`);\n\n// Confirm experience without saving (just delete)\nawait client.spaces.confirmExperience('space-uuid', 'experience-uuid', {\n save: false\n});\n"
1751+
} ],
1752+
"x-codegen-request-body-name" : "request"
1753+
}
1754+
},
16311755
"/space/{space_id}/experience_search" : {
16321756
"get" : {
16331757
"description" : "Retrieve the experience search results for a given query within a space by its ID",
@@ -1922,6 +2046,15 @@
19222046
},
19232047
"type" : "object"
19242048
},
2049+
"handler.ConfirmExperienceReq" : {
2050+
"properties" : {
2051+
"save" : {
2052+
"type" : "boolean"
2053+
}
2054+
},
2055+
"required" : [ "save" ],
2056+
"type" : "object"
2057+
},
19252058
"handler.ConnectToSpaceReq" : {
19262059
"properties" : {
19272060
"space_id" : {
@@ -2280,6 +2413,30 @@
22802413
},
22812414
"type" : "object"
22822415
},
2416+
"model.ExperienceConfirmation" : {
2417+
"properties" : {
2418+
"created_at" : {
2419+
"type" : "string"
2420+
},
2421+
"experience_data" : {
2422+
"properties" : { },
2423+
"type" : "object"
2424+
},
2425+
"id" : {
2426+
"type" : "string"
2427+
},
2428+
"space_id" : {
2429+
"type" : "string"
2430+
},
2431+
"task_id" : {
2432+
"type" : "string"
2433+
},
2434+
"updated_at" : {
2435+
"type" : "string"
2436+
}
2437+
},
2438+
"type" : "object"
2439+
},
22832440
"model.Message" : {
22842441
"properties" : {
22852442
"created_at" : {
@@ -2473,6 +2630,23 @@
24732630
},
24742631
"type" : "object"
24752632
},
2633+
"service.ListExperienceConfirmationsOutput" : {
2634+
"properties" : {
2635+
"has_more" : {
2636+
"type" : "boolean"
2637+
},
2638+
"items" : {
2639+
"items" : {
2640+
"$ref" : "#/components/schemas/model.ExperienceConfirmation"
2641+
},
2642+
"type" : "array"
2643+
},
2644+
"next_cursor" : {
2645+
"type" : "string"
2646+
}
2647+
},
2648+
"type" : "object"
2649+
},
24762650
"service.ListSessionsOutput" : {
24772651
"properties" : {
24782652
"has_more" : {
@@ -2782,6 +2956,30 @@
27822956
"type" : "object"
27832957
} ]
27842958
},
2959+
"_space__space_id__experience_confirmations_get_200_response" : {
2960+
"allOf" : [ {
2961+
"$ref" : "#/components/schemas/serializer.Response"
2962+
}, {
2963+
"properties" : {
2964+
"data" : {
2965+
"$ref" : "#/components/schemas/service.ListExperienceConfirmationsOutput"
2966+
}
2967+
},
2968+
"type" : "object"
2969+
} ]
2970+
},
2971+
"_space__space_id__experience_confirmations__experience_id__patch_200_response" : {
2972+
"allOf" : [ {
2973+
"$ref" : "#/components/schemas/serializer.Response"
2974+
}, {
2975+
"properties" : {
2976+
"data" : {
2977+
"$ref" : "#/components/schemas/model.ExperienceConfirmation"
2978+
}
2979+
},
2980+
"type" : "object"
2981+
} ]
2982+
},
27852983
"_space__space_id__experience_search_get_200_response" : {
27862984
"allOf" : [ {
27872985
"$ref" : "#/components/schemas/serializer.Response"

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
{
6161
"group": "Advance",
6262
"pages": [
63+
"learn/advance/wait-user",
6364
"learn/advance/design-complex",
6465
"learn/advance/experience-agent"
6566
]

docs/learn/advance/wait-user.mdx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: "Wait for User Confirmation"
3+
description: "Acontext can wait for user confirmation before actually leanring skills"
4+
---
5+
6+
By default, Acontext will automatically learn skills from completed tasks.
7+
But this might not be what the user wants, some agent apps, like Manus, provide a way to let users decide whether to learn the skills or not.
8+
9+
Acontext also supports this.
10+
11+
12+
## Enable User Confirmation
13+
You can enable user confirmation by setting the `project_enable_user_confirmation_on_new_experiences` to `true` in the project settings.
14+
15+
```yaml config.yaml
16+
project_enable_user_confirmation_on_new_experiences: true
17+
```
18+
19+
## Obtain the Unconfirmed Experiences
20+
21+
Once you enable it, for every worth-learning experience, Acontext will wait for user confirmation before actually learning skills.
22+
You can obtain the unconfirmed experiences by calling the `get_unconfirmed_experiences` method.
23+
24+
<CodeGroup>
25+
```python Python
26+
experiences = client.spaces.get_unconfirmed_experiences(space_id=space.id)
27+
28+
for e in experiences.items:
29+
print(f"{e.id}: {e.experience_data}")
30+
```
31+
32+
```typescript TypeScript
33+
const experiences = await client.spaces.getUnconfirmedExperiences(spaceId);
34+
35+
for (const e of experiences.items) {
36+
console.log(`${e.id}: ${JSON.stringify(e.experience_data)}`);
37+
}
38+
```
39+
</CodeGroup>
40+
41+
You can render those experiences in UI and let users decide whether to learn the skills or not.
42+
43+
Experience data consists of the following fields:
44+
```json experience_data
45+
{
46+
"type": "sop",
47+
"data": {
48+
"use_when": "Implement google authentication",
49+
"preferences": "Use NextAuth;",
50+
"tool_sops": [
51+
{
52+
"tool_name": "ls",
53+
"action": "find the middleware folder",
54+
},
55+
...
56+
]
57+
}
58+
```
59+
60+
## Confirm the Experience
61+
Once user decided, you can confirm the experience by calling the `confirm_experience` method.
62+
63+
<CodeGroup>
64+
```python Python
65+
# Use this Experience
66+
client.spaces.confirm_experience(space_id=space.id, experience_id=experience.id, save=True)
67+
# Reject this Experience
68+
client.spaces.confirm_experience(space_id=space.id, experience_id=experience.id, save=False)
69+
```
70+
71+
```typescript TypeScript
72+
// Use this Experience
73+
await client.spaces.confirmExperience(spaceId, experienceId, { save: true });
74+
// Reject this Experience
75+
await client.spaces.confirmExperience(spaceId, experienceId, { save: false });
76+
```
77+
</CodeGroup>

src/client/acontext-py/src/acontext/resources/async_spaces.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from .._utils import build_params
99
from ..client_types import AsyncRequesterProtocol
1010
from ..types.space import (
11+
ExperienceConfirmation,
12+
ListExperienceConfirmationsOutput,
1113
ListSpacesOutput,
1214
SearchResultBlockItem,
1315
Space,
@@ -186,3 +188,60 @@ async def semantic_grep(
186188
"GET", f"/space/{space_id}/semantic_grep", params=params or None
187189
)
188190
return [SearchResultBlockItem.model_validate(item) for item in data]
191+
192+
async def get_unconfirmed_experiences(
193+
self,
194+
space_id: str,
195+
*,
196+
limit: int | None = None,
197+
cursor: str | None = None,
198+
time_desc: bool | None = None,
199+
) -> ListExperienceConfirmationsOutput:
200+
"""Get all unconfirmed experiences in a space with cursor-based pagination.
201+
202+
Args:
203+
space_id: The UUID of the space.
204+
limit: Maximum number of confirmations to return (1-200, default 20).
205+
cursor: Cursor for pagination. Use the cursor from the previous response to get the next page.
206+
time_desc: Order by created_at descending if True, ascending if False (default False).
207+
208+
Returns:
209+
ListExperienceConfirmationsOutput containing the list of experience confirmations and pagination information.
210+
"""
211+
params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
212+
data = await self._requester.request(
213+
"GET",
214+
f"/space/{space_id}/experience_confirmations",
215+
params=params or None,
216+
)
217+
return ListExperienceConfirmationsOutput.model_validate(data)
218+
219+
async def confirm_experience(
220+
self,
221+
space_id: str,
222+
experience_id: str,
223+
*,
224+
save: bool,
225+
) -> ExperienceConfirmation | None:
226+
"""Confirm an experience confirmation.
227+
228+
If save is False, delete the row. If save is True, get the data first,
229+
then delete the row.
230+
231+
Args:
232+
space_id: The UUID of the space.
233+
experience_id: The UUID of the experience confirmation.
234+
save: If True, get data before deleting. If False, just delete.
235+
236+
Returns:
237+
ExperienceConfirmation object if save is True, None otherwise.
238+
"""
239+
payload = {"save": save}
240+
data = await self._requester.request(
241+
"PATCH",
242+
f"/space/{space_id}/experience_confirmations/{experience_id}",
243+
json_data=payload,
244+
)
245+
if data is None:
246+
return None
247+
return ExperienceConfirmation.model_validate(data)

0 commit comments

Comments
 (0)