django-agent-studio 0.1.4__py3-none-any.whl → 0.1.8__py3-none-any.whl

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.
@@ -92,6 +92,10 @@ class DynamicAgentRuntime(AgentRuntime):
92
92
  """Execute the agent with the dynamic configuration and agentic loop."""
93
93
  config = self.config
94
94
 
95
+ # Check if memory is enabled (default: True)
96
+ extra_config = config.get("extra", {})
97
+ memory_enabled = extra_config.get("memory_enabled", True)
98
+
95
99
  # Build the messages list
96
100
  messages = []
97
101
 
@@ -108,12 +112,14 @@ class DynamicAgentRuntime(AgentRuntime):
108
112
  if rag_context:
109
113
  system_prompt = f"{system_prompt}\n\n{rag_context}"
110
114
 
111
- # Add conversation memories (if we have a conversation_id and user)
112
- memory_store = await self._get_memory_store(ctx)
113
- if memory_store:
114
- memory_context = await self._recall_memories(memory_store)
115
- if memory_context:
116
- system_prompt = f"{system_prompt}\n\n{memory_context}"
115
+ # Add conversation memories (if memory is enabled and we have context)
116
+ memory_store = None
117
+ if memory_enabled:
118
+ memory_store = await self._get_memory_store(ctx)
119
+ if memory_store:
120
+ memory_context = await self._recall_memories(memory_store)
121
+ if memory_context:
122
+ system_prompt = f"{system_prompt}\n\n{memory_context}"
117
123
 
118
124
  if system_prompt:
119
125
  messages.append({"role": "system", "content": system_prompt})
@@ -121,9 +127,10 @@ class DynamicAgentRuntime(AgentRuntime):
121
127
  # Add conversation history
122
128
  messages.extend(ctx.input_messages)
123
129
 
124
- # Build tool schemas - include memory tool
130
+ # Build tool schemas - include memory tool only if memory is enabled
125
131
  tools = self._build_tool_schemas(config)
126
- tools.append(MEMORY_TOOL_SCHEMA) # Add remember tool to all designed agents
132
+ if memory_enabled:
133
+ tools.append(MEMORY_TOOL_SCHEMA)
127
134
 
128
135
  tool_map = self._build_tool_map(config) # Maps tool name to execution info
129
136
 
@@ -242,4 +242,55 @@ urlpatterns = [
242
242
  views.AgentSystemDiscoverView.as_view(),
243
243
  name="discover_system",
244
244
  ),
245
+
246
+ # ==========================================================================
247
+ # Spec Document Endpoints
248
+ # ==========================================================================
249
+
250
+ # List and create spec documents
251
+ path(
252
+ "spec-documents/",
253
+ views.SpecDocumentListCreateView.as_view(),
254
+ name="spec_document_list",
255
+ ),
256
+ path(
257
+ "spec-documents/<uuid:pk>/",
258
+ views.SpecDocumentDetailView.as_view(),
259
+ name="spec_document_detail",
260
+ ),
261
+
262
+ # Link/unlink to agent
263
+ path(
264
+ "spec-documents/<uuid:pk>/link/",
265
+ views.SpecDocumentLinkView.as_view(),
266
+ name="spec_document_link",
267
+ ),
268
+
269
+ # Version history
270
+ path(
271
+ "spec-documents/<uuid:pk>/history/",
272
+ views.SpecDocumentHistoryView.as_view(),
273
+ name="spec_document_history",
274
+ ),
275
+
276
+ # Restore version
277
+ path(
278
+ "spec-documents/<uuid:pk>/restore/",
279
+ views.SpecDocumentRestoreView.as_view(),
280
+ name="spec_document_restore",
281
+ ),
282
+
283
+ # Tree view
284
+ path(
285
+ "spec-documents/tree/",
286
+ views.SpecDocumentTreeView.as_view(),
287
+ name="spec_document_tree",
288
+ ),
289
+
290
+ # Render as markdown
291
+ path(
292
+ "spec-documents/render/",
293
+ views.SpecDocumentRenderView.as_view(),
294
+ name="spec_document_render",
295
+ ),
245
296
  ]
@@ -1546,3 +1546,382 @@ class AgentSystemDiscoverView(APIView):
1546
1546
  ],
1547
1547
  'total_count': len(agents),
1548
1548
  })
1549
+
1550
+
1551
+ # =============================================================================
1552
+ # Spec Document Views
1553
+ # =============================================================================
1554
+
1555
+ class SpecDocumentListCreateView(APIView):
1556
+ """List all spec documents or create a new one."""
1557
+ permission_classes = [IsAuthenticated]
1558
+
1559
+ def get(self, request):
1560
+ from django_agent_runtime.models import SpecDocument
1561
+
1562
+ root_only = request.query_params.get('root_only', 'false').lower() == 'true'
1563
+ linked_only = request.query_params.get('linked_only', 'false').lower() == 'true'
1564
+
1565
+ if root_only:
1566
+ docs = SpecDocument.objects.filter(parent__isnull=True).select_related('linked_agent').order_by('order', 'title')
1567
+ else:
1568
+ docs = SpecDocument.objects.all().select_related('linked_agent', 'parent').order_by('parent_id', 'order', 'title')
1569
+
1570
+ if linked_only:
1571
+ docs = docs.filter(linked_agent__isnull=False)
1572
+
1573
+ result = []
1574
+ for doc in docs:
1575
+ item = {
1576
+ 'id': str(doc.id),
1577
+ 'title': doc.title,
1578
+ 'parent_id': str(doc.parent_id) if doc.parent_id else None,
1579
+ 'order': doc.order,
1580
+ 'current_version': doc.current_version,
1581
+ 'has_content': bool(doc.content),
1582
+ 'content_preview': doc.content[:100] + '...' if len(doc.content) > 100 else doc.content,
1583
+ 'created_at': doc.created_at.isoformat(),
1584
+ 'updated_at': doc.updated_at.isoformat(),
1585
+ }
1586
+ if doc.linked_agent:
1587
+ item['linked_agent'] = {
1588
+ 'id': str(doc.linked_agent.id),
1589
+ 'slug': doc.linked_agent.slug,
1590
+ 'name': doc.linked_agent.name,
1591
+ }
1592
+ result.append(item)
1593
+
1594
+ return Response({'documents': result, 'count': len(result)})
1595
+
1596
+ def post(self, request):
1597
+ from django_agent_runtime.models import SpecDocument
1598
+
1599
+ title = request.data.get('title')
1600
+ if not title:
1601
+ return Response({'error': 'title is required'}, status=status.HTTP_400_BAD_REQUEST)
1602
+
1603
+ content = request.data.get('content', '')
1604
+ parent_id = request.data.get('parent_id')
1605
+ linked_agent_id = request.data.get('linked_agent_id')
1606
+ order = request.data.get('order', 0)
1607
+
1608
+ parent = None
1609
+ if parent_id:
1610
+ try:
1611
+ parent = SpecDocument.objects.get(id=parent_id)
1612
+ except SpecDocument.DoesNotExist:
1613
+ return Response({'error': f'Parent document not found: {parent_id}'}, status=status.HTTP_404_NOT_FOUND)
1614
+
1615
+ linked_agent = None
1616
+ if linked_agent_id:
1617
+ try:
1618
+ linked_agent = AgentDefinition.objects.get(id=linked_agent_id)
1619
+ except AgentDefinition.DoesNotExist:
1620
+ return Response({'error': f'Agent not found: {linked_agent_id}'}, status=status.HTTP_404_NOT_FOUND)
1621
+
1622
+ doc = SpecDocument.objects.create(
1623
+ title=title,
1624
+ content=content,
1625
+ parent=parent,
1626
+ linked_agent=linked_agent,
1627
+ order=order,
1628
+ owner=request.user,
1629
+ )
1630
+
1631
+ return Response({
1632
+ 'id': str(doc.id),
1633
+ 'title': doc.title,
1634
+ 'current_version': doc.current_version,
1635
+ 'message': f'Created spec document: {title}',
1636
+ }, status=status.HTTP_201_CREATED)
1637
+
1638
+
1639
+ class SpecDocumentDetailView(APIView):
1640
+ """Get, update, or delete a spec document."""
1641
+ permission_classes = [IsAuthenticated]
1642
+
1643
+ def get(self, request, pk):
1644
+ from django_agent_runtime.models import SpecDocument
1645
+
1646
+ try:
1647
+ doc = SpecDocument.objects.select_related('linked_agent', 'parent').get(id=pk)
1648
+ except SpecDocument.DoesNotExist:
1649
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1650
+
1651
+ include_children = request.query_params.get('include_children', 'false').lower() == 'true'
1652
+ render_markdown = request.query_params.get('render_markdown', 'false').lower() == 'true'
1653
+
1654
+ result = {
1655
+ 'id': str(doc.id),
1656
+ 'title': doc.title,
1657
+ 'content': doc.content,
1658
+ 'parent_id': str(doc.parent_id) if doc.parent_id else None,
1659
+ 'order': doc.order,
1660
+ 'current_version': doc.current_version,
1661
+ 'full_path': doc.get_full_path(),
1662
+ 'created_at': doc.created_at.isoformat(),
1663
+ 'updated_at': doc.updated_at.isoformat(),
1664
+ }
1665
+
1666
+ if doc.linked_agent:
1667
+ result['linked_agent'] = {
1668
+ 'id': str(doc.linked_agent.id),
1669
+ 'slug': doc.linked_agent.slug,
1670
+ 'name': doc.linked_agent.name,
1671
+ }
1672
+
1673
+ if include_children:
1674
+ descendants = doc.get_descendants()
1675
+ result['children'] = [
1676
+ {
1677
+ 'id': str(d.id),
1678
+ 'title': d.title,
1679
+ 'parent_id': str(d.parent_id) if d.parent_id else None,
1680
+ 'has_content': bool(d.content),
1681
+ 'linked_agent_slug': d.linked_agent.slug if d.linked_agent else None,
1682
+ }
1683
+ for d in descendants
1684
+ ]
1685
+
1686
+ if render_markdown:
1687
+ result['rendered_markdown'] = doc.render_tree_as_markdown()
1688
+
1689
+ return Response(result)
1690
+
1691
+ def put(self, request, pk):
1692
+ from django_agent_runtime.models import SpecDocument
1693
+
1694
+ try:
1695
+ doc = SpecDocument.objects.get(id=pk)
1696
+ except SpecDocument.DoesNotExist:
1697
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1698
+
1699
+ changes = []
1700
+ if 'title' in request.data:
1701
+ doc.title = request.data['title']
1702
+ changes.append('title')
1703
+ if 'content' in request.data:
1704
+ doc.content = request.data['content']
1705
+ changes.append('content')
1706
+ if 'order' in request.data:
1707
+ doc.order = request.data['order']
1708
+ changes.append('order')
1709
+ if 'parent_id' in request.data:
1710
+ parent_id = request.data['parent_id']
1711
+ if parent_id:
1712
+ try:
1713
+ doc.parent = SpecDocument.objects.get(id=parent_id)
1714
+ except SpecDocument.DoesNotExist:
1715
+ return Response({'error': f'Parent not found: {parent_id}'}, status=status.HTTP_404_NOT_FOUND)
1716
+ else:
1717
+ doc.parent = None
1718
+ changes.append('parent')
1719
+
1720
+ if changes:
1721
+ doc.save()
1722
+ return Response({
1723
+ 'id': str(doc.id),
1724
+ 'title': doc.title,
1725
+ 'current_version': doc.current_version,
1726
+ 'changes': changes,
1727
+ 'message': f'Updated: {", ".join(changes)}',
1728
+ })
1729
+ else:
1730
+ return Response({'message': 'No changes specified'})
1731
+
1732
+ def delete(self, request, pk):
1733
+ from django_agent_runtime.models import SpecDocument
1734
+
1735
+ try:
1736
+ doc = SpecDocument.objects.get(id=pk)
1737
+ except SpecDocument.DoesNotExist:
1738
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1739
+
1740
+ title = doc.title
1741
+ children_count = doc.children.count()
1742
+ doc.delete()
1743
+
1744
+ return Response({
1745
+ 'message': f'Deleted document: {title}',
1746
+ 'deleted_children': children_count,
1747
+ })
1748
+
1749
+
1750
+ class SpecDocumentLinkView(APIView):
1751
+ """Link or unlink a spec document to/from an agent."""
1752
+ permission_classes = [IsAuthenticated]
1753
+
1754
+ def post(self, request, pk):
1755
+ from django_agent_runtime.models import SpecDocument
1756
+
1757
+ try:
1758
+ doc = SpecDocument.objects.get(id=pk)
1759
+ except SpecDocument.DoesNotExist:
1760
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1761
+
1762
+ agent_id = request.data.get('agent_id')
1763
+ if not agent_id:
1764
+ return Response({'error': 'agent_id is required'}, status=status.HTTP_400_BAD_REQUEST)
1765
+
1766
+ try:
1767
+ agent = AgentDefinition.objects.get(id=agent_id)
1768
+ except AgentDefinition.DoesNotExist:
1769
+ return Response({'error': f'Agent not found: {agent_id}'}, status=status.HTTP_404_NOT_FOUND)
1770
+
1771
+ doc.linked_agent = agent
1772
+ doc.save()
1773
+
1774
+ return Response({
1775
+ 'message': f"Linked '{doc.title}' to agent '{agent.name}'",
1776
+ 'document_id': str(doc.id),
1777
+ 'agent_id': str(agent.id),
1778
+ 'agent_slug': agent.slug,
1779
+ })
1780
+
1781
+ def delete(self, request, pk):
1782
+ from django_agent_runtime.models import SpecDocument
1783
+
1784
+ try:
1785
+ doc = SpecDocument.objects.select_related('linked_agent').get(id=pk)
1786
+ except SpecDocument.DoesNotExist:
1787
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1788
+
1789
+ if not doc.linked_agent:
1790
+ return Response({'message': 'Document is not linked to any agent'})
1791
+
1792
+ old_agent_name = doc.linked_agent.name
1793
+ doc.linked_agent = None
1794
+ doc.save()
1795
+
1796
+ return Response({
1797
+ 'message': f"Unlinked '{doc.title}' from agent '{old_agent_name}'",
1798
+ 'document_id': str(doc.id),
1799
+ })
1800
+
1801
+
1802
+ class SpecDocumentHistoryView(APIView):
1803
+ """Get version history of a spec document."""
1804
+ permission_classes = [IsAuthenticated]
1805
+
1806
+ def get(self, request, pk):
1807
+ from django_agent_runtime.models import SpecDocument
1808
+
1809
+ try:
1810
+ doc = SpecDocument.objects.get(id=pk)
1811
+ except SpecDocument.DoesNotExist:
1812
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1813
+
1814
+ limit = int(request.query_params.get('limit', 20))
1815
+ versions = doc.versions.all()[:limit]
1816
+
1817
+ return Response({
1818
+ 'document_id': str(doc.id),
1819
+ 'document_title': doc.title,
1820
+ 'current_version': doc.current_version,
1821
+ 'versions': [
1822
+ {
1823
+ 'version_number': v.version_number,
1824
+ 'title': v.title,
1825
+ 'content_preview': v.content[:200] + '...' if len(v.content) > 200 else v.content,
1826
+ 'created_at': v.created_at.isoformat(),
1827
+ 'change_summary': v.change_summary or '',
1828
+ }
1829
+ for v in versions
1830
+ ],
1831
+ })
1832
+
1833
+
1834
+ class SpecDocumentRestoreView(APIView):
1835
+ """Restore a spec document to a previous version."""
1836
+ permission_classes = [IsAuthenticated]
1837
+
1838
+ def post(self, request, pk):
1839
+ from django_agent_runtime.models import SpecDocument, SpecDocumentVersion
1840
+
1841
+ try:
1842
+ doc = SpecDocument.objects.get(id=pk)
1843
+ except SpecDocument.DoesNotExist:
1844
+ return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
1845
+
1846
+ version_number = request.data.get('version_number')
1847
+ if version_number is None:
1848
+ return Response({'error': 'version_number is required'}, status=status.HTTP_400_BAD_REQUEST)
1849
+
1850
+ try:
1851
+ version = doc.versions.get(version_number=version_number)
1852
+ except SpecDocumentVersion.DoesNotExist:
1853
+ return Response({'error': f'Version not found: {version_number}'}, status=status.HTTP_404_NOT_FOUND)
1854
+
1855
+ version.restore()
1856
+ doc.refresh_from_db()
1857
+
1858
+ return Response({
1859
+ 'message': f'Restored document to version {version_number}',
1860
+ 'document_id': str(doc.id),
1861
+ 'restored_from_version': version_number,
1862
+ 'new_version': doc.current_version,
1863
+ })
1864
+
1865
+
1866
+ class SpecDocumentTreeView(APIView):
1867
+ """Get the full document tree structure."""
1868
+ permission_classes = [IsAuthenticated]
1869
+
1870
+ def get(self, request):
1871
+ from django_agent_runtime.models import SpecDocument
1872
+
1873
+ def build_tree(parent_id=None):
1874
+ if parent_id:
1875
+ docs = SpecDocument.objects.filter(parent_id=parent_id).select_related('linked_agent').order_by('order', 'title')
1876
+ else:
1877
+ docs = SpecDocument.objects.filter(parent__isnull=True).select_related('linked_agent').order_by('order', 'title')
1878
+
1879
+ result = []
1880
+ for doc in docs:
1881
+ node = {
1882
+ 'id': str(doc.id),
1883
+ 'title': doc.title,
1884
+ 'has_content': bool(doc.content),
1885
+ 'order': doc.order,
1886
+ 'linked_agent': {
1887
+ 'id': str(doc.linked_agent.id),
1888
+ 'slug': doc.linked_agent.slug,
1889
+ 'name': doc.linked_agent.name,
1890
+ } if doc.linked_agent else None,
1891
+ 'children': build_tree(doc.id),
1892
+ }
1893
+ result.append(node)
1894
+ return result
1895
+
1896
+ tree = build_tree()
1897
+ return Response({'tree': tree})
1898
+
1899
+
1900
+ class SpecDocumentRenderView(APIView):
1901
+ """Render all spec documents as a single markdown document."""
1902
+ permission_classes = [IsAuthenticated]
1903
+
1904
+ def get(self, request):
1905
+ from django_agent_runtime.models import SpecDocument
1906
+
1907
+ root_id = request.query_params.get('root_id')
1908
+
1909
+ if root_id:
1910
+ try:
1911
+ doc = SpecDocument.objects.get(id=root_id)
1912
+ markdown = doc.render_tree_as_markdown()
1913
+ return Response({
1914
+ 'markdown': markdown,
1915
+ 'root_document': doc.title,
1916
+ })
1917
+ except SpecDocument.DoesNotExist:
1918
+ return Response({'error': f'Document not found: {root_id}'}, status=status.HTTP_404_NOT_FOUND)
1919
+ else:
1920
+ roots = SpecDocument.objects.filter(parent__isnull=True).order_by('order', 'title')
1921
+ parts = [root.render_tree_as_markdown() for root in roots]
1922
+
1923
+ return Response({
1924
+ 'markdown': '\n\n---\n\n'.join(parts),
1925
+ 'root_count': len(parts),
1926
+ 'roots': [{'id': str(r.id), 'title': r.title} for r in roots],
1927
+ })