sagent 0.1.0__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.
Files changed (129) hide show
  1. sagent/__init__.py +187 -0
  2. sagent/agent/__init__.py +37 -0
  3. sagent/agent/agent.py +1729 -0
  4. sagent/agent/compaction.py +170 -0
  5. sagent/agent/cost_tracker.py +55 -0
  6. sagent/agent/dispatch.py +314 -0
  7. sagent/agent/retry.py +352 -0
  8. sagent/agent/session_io.py +657 -0
  9. sagent/agents_md.py +454 -0
  10. sagent/assets/codex.yaml +39 -0
  11. sagent/assets/custom/compactor.md +107 -0
  12. sagent/assets/custom/compactor_continuation.md +3 -0
  13. sagent/assets/custom/compactor_no_tools.md +6 -0
  14. sagent/assets/custom/compactor_no_tools_reminder.md +1 -0
  15. sagent/assets/custom/compactor_partial.md +72 -0
  16. sagent/assets/custom/compactor_system.md +1 -0
  17. sagent/assets/custom/prompt.md +65 -0
  18. sagent/assets/custom/prompt_ant_code_style.md +7 -0
  19. sagent/assets/custom/prompt_ant_false_claims.md +1 -0
  20. sagent/assets/custom/prompt_ant_interruptions.md +1 -0
  21. sagent/assets/custom/prompt_ant_issue_share.md +1 -0
  22. sagent/assets/custom/prompt_ant_misconception.md +1 -0
  23. sagent/assets/custom/prompt_env.md +9 -0
  24. sagent/assets/custom/tools_agentself.md +31 -0
  25. sagent/assets/custom/tools_agentsend.md +20 -0
  26. sagent/assets/custom/tools_agentspawn.md +53 -0
  27. sagent/assets/custom/tools_backgroundtask.md +20 -0
  28. sagent/assets/custom/tools_bash.md +43 -0
  29. sagent/assets/custom/tools_edit.md +9 -0
  30. sagent/assets/custom/tools_glob.md +5 -0
  31. sagent/assets/custom/tools_grep.md +13 -0
  32. sagent/assets/custom/tools_list.md +7 -0
  33. sagent/assets/custom/tools_paperauthor.md +37 -0
  34. sagent/assets/custom/tools_paperdetails.md +48 -0
  35. sagent/assets/custom/tools_paperfetch.md +31 -0
  36. sagent/assets/custom/tools_papersearch.md +48 -0
  37. sagent/assets/custom/tools_read.md +17 -0
  38. sagent/assets/custom/tools_webfetch.md +14 -0
  39. sagent/assets/custom/tools_websearch.md +25 -0
  40. sagent/assets/custom/tools_write.md +7 -0
  41. sagent/assets/persona/default.md +18 -0
  42. sagent/assets/sagent.yaml +39 -0
  43. sagent/bin/__init__.py +1 -0
  44. sagent/bin/check_wheel.py +66 -0
  45. sagent/bin/cli.py +494 -0
  46. sagent/bin/slack.py +1041 -0
  47. sagent/bin/verify_models.py +299 -0
  48. sagent/compactor.py +662 -0
  49. sagent/custom_exceptions.py +91 -0
  50. sagent/custom_types.py +686 -0
  51. sagent/lib/__init__.py +7 -0
  52. sagent/lib/apikey.py +9 -0
  53. sagent/lib/asyncio_collections.py +95 -0
  54. sagent/lib/atomic_file.py +123 -0
  55. sagent/lib/compaction.py +141 -0
  56. sagent/lib/debug_log.py +149 -0
  57. sagent/lib/descriptors.py +336 -0
  58. sagent/lib/dotsagent.py +59 -0
  59. sagent/lib/env.py +21 -0
  60. sagent/lib/image.py +370 -0
  61. sagent/lib/json.py +146 -0
  62. sagent/lib/lazy_import.py +49 -0
  63. sagent/lib/message.py +175 -0
  64. sagent/lib/testing.py +60 -0
  65. sagent/lib/web/__init__.py +16 -0
  66. sagent/lib/web/fetch.py +515 -0
  67. sagent/lib/web/search.py +165 -0
  68. sagent/memory.py +203 -0
  69. sagent/prompt.py +240 -0
  70. sagent/providers/__init__.py +54 -0
  71. sagent/providers/anthropic.py +1081 -0
  72. sagent/providers/dashscope.py +152 -0
  73. sagent/providers/google.py +734 -0
  74. sagent/providers/lib/__init__.py +24 -0
  75. sagent/providers/lib/cost.py +54 -0
  76. sagent/providers/lib/id_remap.py +47 -0
  77. sagent/providers/lib/stop_reason.py +117 -0
  78. sagent/providers/minimax.py +82 -0
  79. sagent/providers/moonshot.py +101 -0
  80. sagent/providers/openai.py +238 -0
  81. sagent/providers/openai_compat.py +857 -0
  82. sagent/providers/providers.py +106 -0
  83. sagent/repl/__init__.py +76 -0
  84. sagent/repl/custom_types.py +24 -0
  85. sagent/repl/handlers.py +305 -0
  86. sagent/repl/input_pane.py +53 -0
  87. sagent/repl/keybindings.py +135 -0
  88. sagent/repl/render.py +145 -0
  89. sagent/repl/render_diff.py +395 -0
  90. sagent/repl/repl.py +241 -0
  91. sagent/repl/slash_commands.py +168 -0
  92. sagent/repl/tight_markdown.py +137 -0
  93. sagent/sessions.py +355 -0
  94. sagent/testing.py +41 -0
  95. sagent/tools/__init__.py +94 -0
  96. sagent/tools/advisor.py +178 -0
  97. sagent/tools/agent_self.py +415 -0
  98. sagent/tools/agent_send.py +144 -0
  99. sagent/tools/agent_spawn.py +804 -0
  100. sagent/tools/background_task.py +223 -0
  101. sagent/tools/bash.py +353 -0
  102. sagent/tools/core.py +1081 -0
  103. sagent/tools/edit.py +305 -0
  104. sagent/tools/glob_tool.py +193 -0
  105. sagent/tools/grep.py +865 -0
  106. sagent/tools/lib/__init__.py +45 -0
  107. sagent/tools/lib/bash.py +840 -0
  108. sagent/tools/lib/pdf.py +227 -0
  109. sagent/tools/linear.py +460 -0
  110. sagent/tools/list.py +197 -0
  111. sagent/tools/paper_author.py +417 -0
  112. sagent/tools/paper_common.py +567 -0
  113. sagent/tools/paper_details.py +367 -0
  114. sagent/tools/paper_fetch.py +228 -0
  115. sagent/tools/paper_search.py +612 -0
  116. sagent/tools/play_audio.py +179 -0
  117. sagent/tools/read.py +539 -0
  118. sagent/tools/result_storage.py +301 -0
  119. sagent/tools/skill.py +318 -0
  120. sagent/tools/slack.py +383 -0
  121. sagent/tools/web_fetch.py +254 -0
  122. sagent/tools/web_search.py +147 -0
  123. sagent/tools/wiki.py +318 -0
  124. sagent/tools/write.py +110 -0
  125. sagent-0.1.0.dist-info/METADATA +407 -0
  126. sagent-0.1.0.dist-info/RECORD +129 -0
  127. sagent-0.1.0.dist-info/WHEEL +4 -0
  128. sagent-0.1.0.dist-info/entry_points.txt +3 -0
  129. sagent-0.1.0.dist-info/licenses/LICENSE +201 -0
sagent/__init__.py ADDED
@@ -0,0 +1,187 @@
1
+ """sagent -- a Python agent library.
2
+
3
+ ::
4
+
5
+ from sagent import tools
6
+ from sagent.agent import Agent
7
+ from sagent.providers import Google
8
+ from sagent.lib.json import json_freeze
9
+
10
+ agent = Agent(
11
+ model=Google.from_env().model("gemini-2.5-flash"),
12
+ system="You are a scientist.",
13
+ tools=[tools.Bash(), tools.Read(), tools.Grep()],
14
+ )
15
+ result = await agent.run(json_freeze({"prompt": "analyze ./data/"}))
16
+
17
+ This module docstring is the authoritative reference for sagent's
18
+ cross-cutting design contracts. Submodule docstrings cover
19
+ implementation details within their scope.
20
+
21
+ Agent
22
+ -----
23
+ An Agent combines four parts:
24
+
25
+ - **Model** -- the LLM endpoint, from a **Provider**.
26
+ - **Tools** -- the agent's capabilities (``run(msg) -> Message``).
27
+ - **System prompt** -- static string or dict of named sections.
28
+ - **Compactor** -- summarizes conversation when the context window
29
+ fills. Optional; without one the agent runs until it hits the
30
+ window limit.
31
+
32
+ Inbox zero
33
+ ----------
34
+ The agent loop pursues inbox zero: process everything, then go idle.
35
+
36
+ ::
37
+
38
+ while True:
39
+ drain inbox -> inject as user messages
40
+ call LLM
41
+ if tool calls: dispatch, loop back to drain
42
+ if inbox empty and LLM done: go idle
43
+
44
+ The inbox is the ONLY entry point for user-message content. User
45
+ prompts, background results, agent-to-agent messages -- all go
46
+ through ``inbox.put()`` / ``inbox.put_left()``. There is ONE
47
+ drain point (``_drain_inbox``) at the top of each loop iteration.
48
+ No other code reads from the inbox or appends user messages to the
49
+ message history.
50
+
51
+ Context-affecting slash commands are inbox messages too. For example,
52
+ ``/clear`` must be enqueued with ``put_left`` and interpreted by
53
+ ``_drain_inbox``; REPL/keybinding code must not clear messages or
54
+ mutate context directly. REPL-local commands that do not touch model
55
+ context, such as ``/model`` display/swap and ``/login``, may be handled
56
+ by the surface before they enter the inbox.
57
+
58
+ Priority is insertion order: user messages go to the front
59
+ (``put_left``), everything else appends at the back.
60
+
61
+ Two queues connect the Agent to its surface (REPL, Slackbot,
62
+ parent agent):
63
+
64
+ - ``inbox`` (Deque[str]) -- inbound.
65
+ - ``events`` (Queue[Message | None]) -- outbound. Every observable
66
+ side effect flows here as a typed Message. ``None`` = request
67
+ boundary.
68
+
69
+ Message
70
+ -------
71
+ The universal unit of exchange. Every input, output, tool call,
72
+ tool result, event, and error is a ``Message``. There is no
73
+ ``ToolResult`` or ``UserTurn`` class -- just ``Message`` with a
74
+ ``descriptor`` that says what it carries.
75
+
76
+ ::
77
+
78
+ type MessageContent = str | bytes | JSON | tuple[Message, ...]
79
+
80
+ @dataclass(frozen=True)
81
+ class Message:
82
+ content: MessageContent
83
+ descriptor: str # MIME-style type tag
84
+ id: int # process-wide auto-increment
85
+ parent_id: int # originating Message.id; -1 = unset
86
+ timestamp: int # nanosecond epoch
87
+
88
+ ``content`` is recursive: a compound message (user message, tool
89
+ result) holds ``tuple[Message, ...]`` of atomic parts. Branch on
90
+ ``descriptor`` to determine what a Message is, never on
91
+ ``isinstance(content)``.
92
+
93
+ Descriptors
94
+ -----------
95
+ The single source of truth for all known descriptors and their
96
+ content-type groups is ``lib/descriptors.py``. See the constants
97
+ ``TEXT_DESCRIPTORS``, ``IMAGE_DESCRIPTORS``, ``BINARY_DESCRIPTORS``,
98
+ ``JSON_DESCRIPTORS``, ``MULTIPART_DESCRIPTORS``, and
99
+ ``ALL_DESCRIPTORS`` defined there.
100
+
101
+ Tool IDs (``application/x-tool-<name>``) are dynamic -- one per tool
102
+ class -- and validated at registration time, not in the registry.
103
+
104
+ Tool contract
105
+ -------------
106
+ Message in, Message out. A tool receives a ``Message`` containing
107
+ the LLM's directive and returns a ``Message`` containing the
108
+ result. That is the entire execution interface.
109
+
110
+ - ``run(msg) -> Message`` -- execute the directive. Returns a
111
+ ``Message`` with ``text/plain`` on success or ``text/x-error``
112
+ on failure. Never raises -- see error policy below.
113
+ - ``directive_schema`` -- JSON Schema describing valid directives.
114
+ - ``prompt() -> str | None`` -- per-request system prompt section.
115
+ ``None`` = no change (avoids cache invalidation).
116
+ - ``help(msg) -> str`` -- human-readable invocation summary.
117
+
118
+ An Agent is itself a Tool (``run`` takes a prompt, returns the
119
+ final response), so agents compose recursively.
120
+
121
+ Model contract
122
+ --------------
123
+ ``buffer()`` and ``stream()`` send a ``ModelRequest`` and return a
124
+ ``ModelResponse``. They raise on failure (``PromptTooLongError``,
125
+ ``RateLimitError``, ``StreamInterruptedError``). The Agent's retry
126
+ and compaction machinery handles recovery.
127
+
128
+ ``is_context_overflow(error)`` returns ``True`` if the error is a
129
+ context-window overflow.
130
+
131
+ Compactor contract
132
+ ------------------
133
+ - ``should_compact()`` -- whether to compact this request.
134
+ - ``compact()`` -- summarize messages into a compact list. Returns
135
+ a failure Message if all retry attempts are exhausted (not raise --
136
+ see error policy below).
137
+ - ``maintain()`` -- between-request context maintenance (e.g. clear
138
+ stale tool results).
139
+
140
+ CompactRestorable
141
+ -----------------
142
+ Optional protocol for tools that need to restore state after
143
+ compaction (e.g. re-inject invoked skill bodies). The Agent calls
144
+ ``post_compact_restore(messages, tool_state, budget_chars=N)`` on
145
+ tools that implement it.
146
+
147
+ Error policy
148
+ ------------
149
+ **If your output enters the conversation, return a Message.
150
+ If your output is infrastructure plumbing, raise.**
151
+
152
+ This yields three patterns:
153
+
154
+ 1. **Tools return error Messages.** A tool's ``run()`` returns
155
+ ``Message`` by contract, so errors are ``text/x-error`` parts.
156
+ The LLM sees the error as a tool result and can self-correct.
157
+ ``_invoke_tool_safe`` is the safety net for unexpected
158
+ exceptions -- it wraps them in ``<tool_use_error>`` so the
159
+ LLM still sees something actionable.
160
+
161
+ 2. **Infrastructure raises.** Model and Provider methods don't
162
+ return Messages -- they return ``ModelResponse`` or build
163
+ objects. Errors are exceptions. The Agent catches and handles:
164
+ retry, compact, disable, or surface to the user. The LLM
165
+ never sees these exceptions directly.
166
+
167
+ 3. **Optional enrichment is individually isolated.** Post-compact
168
+ steps (file re-attachment, tool state restoration, background
169
+ status) are best-effort. Each is wrapped in its own try/except
170
+ that logs and continues. One failure does not cascade -- it
171
+ must not disable compaction or crash the conversation.
172
+
173
+ Compactor is illustrative: ``compact()`` returns ``list[Message]``
174
+ (a Message-returning interface), so on exhausted retries it
175
+ returns a failure Message rather than raising. The LLM needs to
176
+ know context was lost so it can recover.
177
+ """
178
+
179
+ from sagent import providers, tools
180
+ from sagent.agent import Agent
181
+
182
+
183
+ __all__ = [
184
+ "Agent",
185
+ "providers",
186
+ "tools",
187
+ ]
@@ -0,0 +1,37 @@
1
+ """Agent: Model + Tools + Prompt + Compactor behind two queues.
2
+
3
+ See ``sagent/__init__.py`` for the Agent architecture, inbox zero
4
+ loop, error policy, and contract definitions.
5
+
6
+ Session persistence
7
+ -------------------
8
+ ``_save_session`` serializes ``_messages`` and metadata to
9
+ ``session.jsonl``. The request loop wraps in ``try/finally`` so
10
+ every exit path persists. Internal mutations (status change,
11
+ compaction, clear) save immediately.
12
+ """
13
+
14
+ from sagent.agent.agent import (
15
+ ERROR_MAX_TOOL_CALL_ROUNDS,
16
+ ERROR_NO_PROMPT,
17
+ QUIT_SENTINEL,
18
+ Agent,
19
+ RunHandle,
20
+ SystemPrompt,
21
+ )
22
+ from sagent.compactor import MICROCOMPACT_KEEP_RECENT
23
+ from sagent.custom_types import ContextBudget
24
+ from sagent.tools.background_task import BackgroundTaskEntry
25
+
26
+
27
+ __all__ = [
28
+ "ERROR_MAX_TOOL_CALL_ROUNDS",
29
+ "ERROR_NO_PROMPT",
30
+ "MICROCOMPACT_KEEP_RECENT",
31
+ "QUIT_SENTINEL",
32
+ "Agent",
33
+ "BackgroundTaskEntry",
34
+ "ContextBudget",
35
+ "RunHandle",
36
+ "SystemPrompt",
37
+ ]