rungent 0.1.0__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.
@@ -0,0 +1,73 @@
1
+ # Python
2
+ .venv/
3
+ venv/
4
+ .uv-cache/
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+ .Python
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .pyright/
13
+ .mypy_cache/
14
+ .coverage
15
+ .coverage.*
16
+ htmlcov/
17
+ .tox/
18
+ .nox/
19
+ dist/
20
+ build/
21
+ wheels/
22
+ *.egg-info/
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Node / pnpm
27
+ node_modules/
28
+ .pnpm-store/
29
+ .pnpm-debug.log*
30
+ npm-debug.log*
31
+ yarn-debug.log*
32
+ yarn-error.log*
33
+ *.tsbuildinfo
34
+
35
+ # Docs / Next / Fumadocs
36
+ .next/
37
+ .turbo/
38
+ .source/
39
+ out/
40
+ apps/docs/.next/
41
+ apps/docs/.source/
42
+
43
+ # Test & coverage (JS)
44
+ coverage/
45
+ *.lcov
46
+
47
+ # Env & secrets
48
+ .env
49
+ .env.*
50
+ !.env.example
51
+ !.env.*.example
52
+ *.pem
53
+ *.key
54
+ credentials.json
55
+ secrets.json
56
+
57
+ # OS / editors
58
+ .DS_Store
59
+ Thumbs.db
60
+ .idea/
61
+ .vscode/*
62
+ !.vscode/extensions.json
63
+ !.vscode/settings.json
64
+ *.swp
65
+ *.swo
66
+ *~
67
+
68
+ # Local / misc
69
+ *.log
70
+ tmp/
71
+ temp/
72
+ .cache/
73
+ .direnv/
rungent-0.1.0/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Rungent Contributors
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
rungent-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: rungent
3
+ Version: 0.1.0
4
+ Summary: A small, typed agent runtime for Python applications
5
+ Project-URL: Homepage, https://github.com/rungent/rungent
6
+ Project-URL: Documentation, https://github.com/rungent/rungent/tree/main/docs
7
+ Project-URL: Repository, https://github.com/rungent/rungent
8
+ Project-URL: Issues, https://github.com/rungent/rungent/issues
9
+ Project-URL: Changelog, https://github.com/rungent/rungent/releases
10
+ Author: Rungent Contributors
11
+ License-Expression: Apache-2.0
12
+ License-File: LICENSE
13
+ Keywords: agent,fastapi,llm,sse,tool-calling
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: httpx>=0.28.0
23
+ Requires-Dist: pydantic>=2.10.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: aiosqlite>=0.20.0; extra == 'dev'
26
+ Requires-Dist: fastapi>=0.115.0; extra == 'dev'
27
+ Requires-Dist: pyright>=1.1.0; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
31
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.36; extra == 'dev'
32
+ Provides-Extra: fastapi
33
+ Requires-Dist: fastapi>=0.115.0; extra == 'fastapi'
34
+ Provides-Extra: sqlalchemy
35
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.36; extra == 'sqlalchemy'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Rungent
39
+
40
+ Rungent is a small, typed agent runtime that embeds into an existing Python application. It
41
+ standardizes tool calling, the harness loop, durable sessions and runs, user interactions,
42
+ deterministic approval gates, and an SSE event protocol.
43
+
44
+ Its headless activity protocol exposes model steps, safe public progress, tools, and interactions as
45
+ persisted events. It never exposes raw provider reasoning or hidden chain of thought.
46
+
47
+ ```python
48
+ from typing import Annotated
49
+
50
+ from rungent import Agent, RunActivity, Runtime, ToolContext, ToolResult, tool
51
+ from rungent.llm import OpenAICompatibleModel
52
+ from rungent.store import MemoryStore
53
+
54
+
55
+ @tool(effect="write", approval="never")
56
+ async def move_place(
57
+ ctx: ToolContext,
58
+ name: Annotated[str, "Exact place name"],
59
+ day_number: Annotated[int | None, "Target day; null means unplanned"],
60
+ ) -> ToolResult:
61
+ await ctx.deps["trips"].move(ctx.resource["trip_id"], name, day_number)
62
+ return ToolResult(message=f"Moved {name}")
63
+
64
+
65
+ agent = Agent(
66
+ name="trip_planner",
67
+ instructions="Help the user update their trip. Use tools for every change.",
68
+ tools=[move_place],
69
+ run_activity=lambda _ctx, _content: RunActivity(
70
+ message="Received the request",
71
+ waiting_message="Still preparing; no changes have been made",
72
+ long_wait_message="The request is detailed; still preparing",
73
+ continuation_message="Preparing the result",
74
+ ),
75
+ )
76
+
77
+ runtime = Runtime(
78
+ agents=[agent],
79
+ model=OpenAICompatibleModel.from_env(),
80
+ store=MemoryStore(),
81
+ )
82
+ ```
83
+
84
+ The canonical documentation lives in `docs/` and is rendered by the Fumadocs app in `apps/docs`.
85
+ AI coding agents should start with [`RUNGENT.md`](RUNGENT.md); the running site also exposes
86
+ `/llms.txt`, `/llms-full.txt`, and per-page raw Markdown under `/markdown/*`.
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ uv sync --all-extras
92
+ uv run pytest -W error
93
+ uv run ruff check src tests
94
+
95
+ pnpm install
96
+ pnpm dev:docs
97
+ pnpm test
98
+ pnpm build
99
+ ```
100
+
101
+ ## Release
102
+
103
+ `rungent` (PyPI) and `@rungent/sdk` (npm) share one semver. Publishing is done by GitHub Actions
104
+ on `v*` tags — do not `uv publish` / `npm publish` from your laptop.
105
+
106
+ ```bash
107
+ ./scripts/release.sh 0.2.0
108
+ git push origin main --tags
109
+ ```
110
+
111
+ One-time Trusted Publishing setup: see [`RELEASING.md`](RELEASING.md).
@@ -0,0 +1,74 @@
1
+ # Rungent
2
+
3
+ Rungent is a small, typed agent runtime that embeds into an existing Python application. It
4
+ standardizes tool calling, the harness loop, durable sessions and runs, user interactions,
5
+ deterministic approval gates, and an SSE event protocol.
6
+
7
+ Its headless activity protocol exposes model steps, safe public progress, tools, and interactions as
8
+ persisted events. It never exposes raw provider reasoning or hidden chain of thought.
9
+
10
+ ```python
11
+ from typing import Annotated
12
+
13
+ from rungent import Agent, RunActivity, Runtime, ToolContext, ToolResult, tool
14
+ from rungent.llm import OpenAICompatibleModel
15
+ from rungent.store import MemoryStore
16
+
17
+
18
+ @tool(effect="write", approval="never")
19
+ async def move_place(
20
+ ctx: ToolContext,
21
+ name: Annotated[str, "Exact place name"],
22
+ day_number: Annotated[int | None, "Target day; null means unplanned"],
23
+ ) -> ToolResult:
24
+ await ctx.deps["trips"].move(ctx.resource["trip_id"], name, day_number)
25
+ return ToolResult(message=f"Moved {name}")
26
+
27
+
28
+ agent = Agent(
29
+ name="trip_planner",
30
+ instructions="Help the user update their trip. Use tools for every change.",
31
+ tools=[move_place],
32
+ run_activity=lambda _ctx, _content: RunActivity(
33
+ message="Received the request",
34
+ waiting_message="Still preparing; no changes have been made",
35
+ long_wait_message="The request is detailed; still preparing",
36
+ continuation_message="Preparing the result",
37
+ ),
38
+ )
39
+
40
+ runtime = Runtime(
41
+ agents=[agent],
42
+ model=OpenAICompatibleModel.from_env(),
43
+ store=MemoryStore(),
44
+ )
45
+ ```
46
+
47
+ The canonical documentation lives in `docs/` and is rendered by the Fumadocs app in `apps/docs`.
48
+ AI coding agents should start with [`RUNGENT.md`](RUNGENT.md); the running site also exposes
49
+ `/llms.txt`, `/llms-full.txt`, and per-page raw Markdown under `/markdown/*`.
50
+
51
+ ## Development
52
+
53
+ ```bash
54
+ uv sync --all-extras
55
+ uv run pytest -W error
56
+ uv run ruff check src tests
57
+
58
+ pnpm install
59
+ pnpm dev:docs
60
+ pnpm test
61
+ pnpm build
62
+ ```
63
+
64
+ ## Release
65
+
66
+ `rungent` (PyPI) and `@rungent/sdk` (npm) share one semver. Publishing is done by GitHub Actions
67
+ on `v*` tags — do not `uv publish` / `npm publish` from your laptop.
68
+
69
+ ```bash
70
+ ./scripts/release.sh 0.2.0
71
+ git push origin main --tags
72
+ ```
73
+
74
+ One-time Trusted Publishing setup: see [`RELEASING.md`](RELEASING.md).
@@ -0,0 +1,99 @@
1
+ # Rungent integration guide for coding agents
2
+
3
+ This is the canonical short context for an AI coding agent integrating Rungent.
4
+
5
+ 1. Define one `Agent` for one product assistant. Do not introduce skills or routing unless the
6
+ product genuinely has multiple independent capability domains.
7
+ 2. Define business operations with `@tool`. The first parameter is `ToolContext`; all other typed
8
+ parameters become the model-facing JSON Schema.
9
+ 3. Declare `effect` and `approval` explicitly. Never infer safety from a function name.
10
+ Every `approval="always"` tool must provide a concrete `confirmation` string or callable that
11
+ identifies the affected object and impact; never ask users to approve only a generic tool title.
12
+ Equivalent successful non-read calls are deduplicated per Run; prefer a batch/count argument and
13
+ use `deduplicate=False` only when identical repeated writes are intentional.
14
+ 4. Put durable service factories in `ToolContext.deps`, stable resource identifiers in
15
+ `ToolContext.resource`, and read the accepted input from `ToolContext.current_input`. After
16
+ `request_input` resumes, `ToolContext.interaction_response`
17
+ contains the runtime-validated interaction ID, kind, prompt, and value for that response only;
18
+ declare `requires_interaction_response=True` when a host operation must reject model-invented
19
+ answers. Rungent exposes that tool only after a real answer and consumes the answer once.
20
+ 5. Use `Runtime` for session/run lifecycle. Do not write a second tool loop. The default bounded
21
+ loop allows 16 model steps. Retryable timeouts, transport failures, HTTP 408/429, and server
22
+ errors retry inside the same step up to three times; empty completions consume a new bounded
23
+ step and never become empty user messages.
24
+ 6. Use the FastAPI router from `rungent.fastapi` when exposing the assistant over HTTP. Run
25
+ creation returns `202` with a Run ID; subscribe separately so disconnecting the event stream
26
+ never cancels execution. Pass `Idempotency-Key` when a client may retry creation.
27
+ 7. Consume the SSE stream with `@rungent/sdk`. Do not parse provider-specific model chunks in the
28
+ frontend.
29
+ 8. Use `request_input` for missing information. Group two to eight independent, already-known
30
+ questions in one `form`; ask sequentially only when a later question depends on an earlier
31
+ answer. A `choice` may declare `multiple` and `allow_custom`; choice responses use
32
+ `{selected: string[], custom?: string}`. Form responses use `{answers: {question_id: value}}`.
33
+ All responses are validated by the runtime. Destructive approvals are generated by Rungent,
34
+ not by prompting the model to ask “confirm?”.
35
+ 9. Rungent exposes public work activity, not raw chain of thought. The model may call the built-in
36
+ `report_progress` alongside business tool calls during multi-step work, never as a dedicated
37
+ model step; model steps, tools, interactions, and progress are
38
+ persisted as ordered public events and projected by the SDK as `state.activities`.
39
+ Terminal Run events settle every unfinished activity; transport failures should call
40
+ `failRungentState` so a stale spinner can never survive an interrupted request.
41
+ Long-running host tools may call `await ctx.report_progress(message, public=...)`; Rungent
42
+ persists and streams each update while the tool is still executing. Updates from one tool call
43
+ share a stable activity ID, remain `running` while work continues, and replace one projected SDK
44
+ row; the ordered event log still retains every update for replay and audit.
45
+ 10. Add deterministic baseline cases before enabling a real model baseline. Export a
46
+ `BaselineSuite` and run `rungent baseline module:suite` in the host project.
47
+ Tool and interaction expectations are exact by default; use their `contains` match modes only
48
+ for live-model cases where safe retries or optional branches are acceptable, and always assert
49
+ final state.
50
+ 11. Read `docs/ai-reference.mdx` for the full public API and `examples/roasea/` for the reference
51
+ application pattern.
52
+ 12. Model user-sized business intents as atomic host tools. Stage large imports in the host
53
+ application, return their diff through `ToolResult.public`, clarify with `request_input`, and
54
+ commit the reviewed draft with one revision-checked atomic tool and one approval. Likewise, a
55
+ reversible “change every leg” request should be one host batch tool, not many single-item calls;
56
+ increasing the model-step limit cannot repair an undersized business interface.
57
+ 13. Model-facing tool results carry `tool_status: success|error`. Only explicit success envelopes
58
+ participate in deduplication; exceptions, timeouts, and `ToolResult(data={"ok": false})` remain
59
+ retryable. Call `client.cancel(runId)` before aborting the stream's `AbortSignal`; the cancel
60
+ endpoint terminates the active drive and persists `run.cancelled`. Treat `cancelled` as a
61
+ distinct terminal state.
62
+ 14. Add trusted provider-specific top-level request fields through
63
+ `OpenAICompatibleModel(extra_body=...)` or the `LLM_EXTRA_BODY` JSON object. Core request fields
64
+ cannot be overridden. For latency-sensitive Qwen runs, for example, use
65
+ `{"enable_thinking":false}`.
66
+ 15. Malformed or truncated tool-argument JSON is retried inside the same model step. Rungent resets
67
+ partial text and asks the provider for compact valid JSON before any tool can execute.
68
+ 16. A host tool may return `ToolResult.interaction=InteractionRequest(...)` with a frozen
69
+ continuation tool. Rungent persists and displays that question immediately, then executes the
70
+ typed `requires_interaction_response` continuation directly after a validated answer without
71
+ spending another model step. Use `allow_skip` only when skipping is safe for the workflow.
72
+ 17. Latency-sensitive agents should declare `run_activity`, returning public `RunActivity`
73
+ messages authored by the host application. Rungent emits the first activity immediately and,
74
+ while a provider stream is silent, updates the same persisted activity after three seconds and
75
+ every five seconds thereafter. Progress activities remain `running` until real model work
76
+ advances or the Run terminates; never use this channel for hidden reasoning. Products that
77
+ require finite deadlines may set per-attempt `model_step_timeout_seconds` and overall
78
+ `model_step_total_timeout_seconds`; retries receive a fresh per-attempt budget.
79
+ 18. Bounded-loop exhaustion keeps the numeric model-step limit in operator state and emits a safe
80
+ `run.failed` envelope with `code=model_step_limit_exceeded` and `retryable=true`. Product UIs
81
+ should localize recovery guidance from the code instead of displaying internals.
82
+ 19. `request_input` rejects numbered alternatives embedded in a `text` prompt; use `choice` so the
83
+ client can render typed options and a custom-answer row. Tool activities expose stable failure
84
+ codes, allowing product UIs to suppress or localize technical validation noise.
85
+ 19. Assistant message content is an opaque string to Rungent and `@rungent/sdk`. The host Agent owns
86
+ its writing-format instructions, and the product frontend owns Markdown parsing, sanitization,
87
+ link policy, and styling. Rungent intentionally does not declare a content format or ship a
88
+ renderer.
89
+ 20. A host tool may return `ToolResult.deferred=DeferredRequest(...)` for durable work that depends
90
+ on a browser or worker observation. Rungent persists the task binding, moves the Run to
91
+ `waiting_external`, and spends no model steps while waiting. Host code reports public progress
92
+ and calls `Runtime.resume_deferred` exactly once with an authoritative terminal result. The
93
+ runtime atomically claims and leases that resumption before returning to the model, so recovery
94
+ workers cannot mistake active continuation work for an interrupted Run. Connect
95
+ `external_task_canceller` when cancelling the Run must cancel host-owned work. Keep domain job
96
+ state in the application, not Rungent.
97
+ 21. Applications with an obvious deterministic entry path may declare `Agent.run_initializer`.
98
+ It returns at most one registered typed `ToolCall` before the first model step; Rungent applies
99
+ the same validation, interaction, deferred, and event rules as it does to model calls.
@@ -0,0 +1,80 @@
1
+ [project]
2
+ name = "rungent"
3
+ version = "0.1.0"
4
+ description = "A small, typed agent runtime for Python applications"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "Rungent Contributors" }]
9
+ keywords = ["agent", "llm", "tool-calling", "fastapi", "sse"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "License :: OSI Approved :: Apache Software License",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = [
20
+ "httpx>=0.28.0",
21
+ "pydantic>=2.10.0",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/rungent/rungent"
26
+ Documentation = "https://github.com/rungent/rungent/tree/main/docs"
27
+ Repository = "https://github.com/rungent/rungent"
28
+ Issues = "https://github.com/rungent/rungent/issues"
29
+ Changelog = "https://github.com/rungent/rungent/releases"
30
+
31
+ [project.optional-dependencies]
32
+ fastapi = ["fastapi>=0.115.0"]
33
+ sqlalchemy = ["sqlalchemy[asyncio]>=2.0.36"]
34
+ dev = [
35
+ "aiosqlite>=0.20.0",
36
+ "fastapi>=0.115.0",
37
+ "pytest>=8.3.0",
38
+ "pytest-asyncio>=0.24.0",
39
+ "pyright>=1.1.0",
40
+ "ruff>=0.8.0",
41
+ "sqlalchemy[asyncio]>=2.0.36",
42
+ ]
43
+
44
+ [project.scripts]
45
+ rungent = "rungent.cli:main"
46
+
47
+ [build-system]
48
+ requires = ["hatchling"]
49
+ build-backend = "hatchling.build"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/rungent"]
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = [
56
+ "/src/rungent",
57
+ "/README.md",
58
+ "/RUNGENT.md",
59
+ "/LICENSE",
60
+ "/pyproject.toml",
61
+ ]
62
+
63
+ [tool.pytest.ini_options]
64
+ asyncio_mode = "auto"
65
+ testpaths = ["tests"]
66
+ filterwarnings = ["error"]
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ target-version = "py311"
71
+
72
+ [tool.ruff.lint]
73
+ select = ["E", "F", "I", "UP"]
74
+
75
+ [tool.pyright]
76
+ pythonVersion = "3.11"
77
+ typeCheckingMode = "standard"
78
+ venvPath = "."
79
+ venv = ".venv"
80
+ extraPaths = ["src"]
@@ -0,0 +1,99 @@
1
+ # Rungent integration guide for coding agents
2
+
3
+ This is the canonical short context for an AI coding agent integrating Rungent.
4
+
5
+ 1. Define one `Agent` for one product assistant. Do not introduce skills or routing unless the
6
+ product genuinely has multiple independent capability domains.
7
+ 2. Define business operations with `@tool`. The first parameter is `ToolContext`; all other typed
8
+ parameters become the model-facing JSON Schema.
9
+ 3. Declare `effect` and `approval` explicitly. Never infer safety from a function name.
10
+ Every `approval="always"` tool must provide a concrete `confirmation` string or callable that
11
+ identifies the affected object and impact; never ask users to approve only a generic tool title.
12
+ Equivalent successful non-read calls are deduplicated per Run; prefer a batch/count argument and
13
+ use `deduplicate=False` only when identical repeated writes are intentional.
14
+ 4. Put durable service factories in `ToolContext.deps`, stable resource identifiers in
15
+ `ToolContext.resource`, and read the accepted input from `ToolContext.current_input`. After
16
+ `request_input` resumes, `ToolContext.interaction_response`
17
+ contains the runtime-validated interaction ID, kind, prompt, and value for that response only;
18
+ declare `requires_interaction_response=True` when a host operation must reject model-invented
19
+ answers. Rungent exposes that tool only after a real answer and consumes the answer once.
20
+ 5. Use `Runtime` for session/run lifecycle. Do not write a second tool loop. The default bounded
21
+ loop allows 16 model steps. Retryable timeouts, transport failures, HTTP 408/429, and server
22
+ errors retry inside the same step up to three times; empty completions consume a new bounded
23
+ step and never become empty user messages.
24
+ 6. Use the FastAPI router from `rungent.fastapi` when exposing the assistant over HTTP. Run
25
+ creation returns `202` with a Run ID; subscribe separately so disconnecting the event stream
26
+ never cancels execution. Pass `Idempotency-Key` when a client may retry creation.
27
+ 7. Consume the SSE stream with `@rungent/sdk`. Do not parse provider-specific model chunks in the
28
+ frontend.
29
+ 8. Use `request_input` for missing information. Group two to eight independent, already-known
30
+ questions in one `form`; ask sequentially only when a later question depends on an earlier
31
+ answer. A `choice` may declare `multiple` and `allow_custom`; choice responses use
32
+ `{selected: string[], custom?: string}`. Form responses use `{answers: {question_id: value}}`.
33
+ All responses are validated by the runtime. Destructive approvals are generated by Rungent,
34
+ not by prompting the model to ask “confirm?”.
35
+ 9. Rungent exposes public work activity, not raw chain of thought. The model may call the built-in
36
+ `report_progress` alongside business tool calls during multi-step work, never as a dedicated
37
+ model step; model steps, tools, interactions, and progress are
38
+ persisted as ordered public events and projected by the SDK as `state.activities`.
39
+ Terminal Run events settle every unfinished activity; transport failures should call
40
+ `failRungentState` so a stale spinner can never survive an interrupted request.
41
+ Long-running host tools may call `await ctx.report_progress(message, public=...)`; Rungent
42
+ persists and streams each update while the tool is still executing. Updates from one tool call
43
+ share a stable activity ID, remain `running` while work continues, and replace one projected SDK
44
+ row; the ordered event log still retains every update for replay and audit.
45
+ 10. Add deterministic baseline cases before enabling a real model baseline. Export a
46
+ `BaselineSuite` and run `rungent baseline module:suite` in the host project.
47
+ Tool and interaction expectations are exact by default; use their `contains` match modes only
48
+ for live-model cases where safe retries or optional branches are acceptable, and always assert
49
+ final state.
50
+ 11. Read `docs/ai-reference.mdx` for the full public API and `examples/roasea/` for the reference
51
+ application pattern.
52
+ 12. Model user-sized business intents as atomic host tools. Stage large imports in the host
53
+ application, return their diff through `ToolResult.public`, clarify with `request_input`, and
54
+ commit the reviewed draft with one revision-checked atomic tool and one approval. Likewise, a
55
+ reversible “change every leg” request should be one host batch tool, not many single-item calls;
56
+ increasing the model-step limit cannot repair an undersized business interface.
57
+ 13. Model-facing tool results carry `tool_status: success|error`. Only explicit success envelopes
58
+ participate in deduplication; exceptions, timeouts, and `ToolResult(data={"ok": false})` remain
59
+ retryable. Call `client.cancel(runId)` before aborting the stream's `AbortSignal`; the cancel
60
+ endpoint terminates the active drive and persists `run.cancelled`. Treat `cancelled` as a
61
+ distinct terminal state.
62
+ 14. Add trusted provider-specific top-level request fields through
63
+ `OpenAICompatibleModel(extra_body=...)` or the `LLM_EXTRA_BODY` JSON object. Core request fields
64
+ cannot be overridden. For latency-sensitive Qwen runs, for example, use
65
+ `{"enable_thinking":false}`.
66
+ 15. Malformed or truncated tool-argument JSON is retried inside the same model step. Rungent resets
67
+ partial text and asks the provider for compact valid JSON before any tool can execute.
68
+ 16. A host tool may return `ToolResult.interaction=InteractionRequest(...)` with a frozen
69
+ continuation tool. Rungent persists and displays that question immediately, then executes the
70
+ typed `requires_interaction_response` continuation directly after a validated answer without
71
+ spending another model step. Use `allow_skip` only when skipping is safe for the workflow.
72
+ 17. Latency-sensitive agents should declare `run_activity`, returning public `RunActivity`
73
+ messages authored by the host application. Rungent emits the first activity immediately and,
74
+ while a provider stream is silent, updates the same persisted activity after three seconds and
75
+ every five seconds thereafter. Progress activities remain `running` until real model work
76
+ advances or the Run terminates; never use this channel for hidden reasoning. Products that
77
+ require finite deadlines may set per-attempt `model_step_timeout_seconds` and overall
78
+ `model_step_total_timeout_seconds`; retries receive a fresh per-attempt budget.
79
+ 18. Bounded-loop exhaustion keeps the numeric model-step limit in operator state and emits a safe
80
+ `run.failed` envelope with `code=model_step_limit_exceeded` and `retryable=true`. Product UIs
81
+ should localize recovery guidance from the code instead of displaying internals.
82
+ 19. `request_input` rejects numbered alternatives embedded in a `text` prompt; use `choice` so the
83
+ client can render typed options and a custom-answer row. Tool activities expose stable failure
84
+ codes, allowing product UIs to suppress or localize technical validation noise.
85
+ 19. Assistant message content is an opaque string to Rungent and `@rungent/sdk`. The host Agent owns
86
+ its writing-format instructions, and the product frontend owns Markdown parsing, sanitization,
87
+ link policy, and styling. Rungent intentionally does not declare a content format or ship a
88
+ renderer.
89
+ 20. A host tool may return `ToolResult.deferred=DeferredRequest(...)` for durable work that depends
90
+ on a browser or worker observation. Rungent persists the task binding, moves the Run to
91
+ `waiting_external`, and spends no model steps while waiting. Host code reports public progress
92
+ and calls `Runtime.resume_deferred` exactly once with an authoritative terminal result. The
93
+ runtime atomically claims and leases that resumption before returning to the model, so recovery
94
+ workers cannot mistake active continuation work for an interrupted Run. Connect
95
+ `external_task_canceller` when cancelling the Run must cancel host-owned work. Keep domain job
96
+ state in the application, not Rungent.
97
+ 21. Applications with an obvious deterministic entry path may declare `Agent.run_initializer`.
98
+ It returns at most one registered typed `ToolCall` before the first model step; Rungent applies
99
+ the same validation, interaction, deferred, and event rules as it does to model calls.
@@ -0,0 +1,30 @@
1
+ from .agent import Agent, RunActivity
2
+ from .runtime import Runtime
3
+ from .state import (
4
+ DeferredRequest,
5
+ Identity,
6
+ InteractionRequest,
7
+ InteractionResponse,
8
+ ToolContinuation,
9
+ ToolResult,
10
+ TrustedInteractionResponse,
11
+ )
12
+ from .tools import ApprovalPolicy, Tool, ToolContext, ToolEffect, tool
13
+
14
+ __all__ = [
15
+ "Agent",
16
+ "ApprovalPolicy",
17
+ "DeferredRequest",
18
+ "Identity",
19
+ "InteractionRequest",
20
+ "InteractionResponse",
21
+ "Runtime",
22
+ "RunActivity",
23
+ "Tool",
24
+ "ToolContext",
25
+ "ToolContinuation",
26
+ "ToolEffect",
27
+ "ToolResult",
28
+ "TrustedInteractionResponse",
29
+ "tool",
30
+ ]
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+ from .state import new_id, now
11
+
12
+
13
+ class Event(BaseModel):
14
+ v: int = 1
15
+ id: str = Field(default_factory=lambda: new_id("evt"))
16
+ seq: int
17
+ type: str
18
+ session_id: str
19
+ run_id: str
20
+ created_at: datetime = Field(default_factory=now)
21
+ data: dict[str, Any] = Field(default_factory=dict)
22
+
23
+
24
+ @dataclass(slots=True)
25
+ class EventEmitter:
26
+ session_id: str
27
+ run_id: str
28
+ sequence: int = 0
29
+
30
+ def emit(self, event_type: str, **data: Any) -> Event:
31
+ self.sequence += 1
32
+ return Event(
33
+ seq=self.sequence,
34
+ type=event_type,
35
+ session_id=self.session_id,
36
+ run_id=self.run_id,
37
+ data=data,
38
+ )
39
+
40
+
41
+ def encode_sse(event: Event) -> str:
42
+ return f"data: {json.dumps(event.model_dump(mode='json'), ensure_ascii=False)}\n\n"