Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
751462e
Python: add TypeSafe Jev structured output sample
eavanvalkenburg Sep 19, 2026
1d0c310
Python: add TypeSafe AI connector package
eavanvalkenburg Sep 21, 2026
932fc90
Python: map TypeSafe response format to questions
eavanvalkenburg Sep 21, 2026
8a77c0c
Python: standardize TypeSafe settings resolution
eavanvalkenburg Sep 21, 2026
0fbcffc
Python: split raw TypeSafe chat client
eavanvalkenburg Sep 21, 2026
1e91f90
Python: add TypeSafe function calling
eavanvalkenburg Sep 21, 2026
2e44b01
Python: validate TypeSafe model before tools
eavanvalkenburg Sep 21, 2026
f26f0d5
Python: return TypeSafe tool results
eavanvalkenburg Sep 21, 2026
b4fb494
Python: support TypeSafe tool roundtrips
eavanvalkenburg Sep 21, 2026
26bdbb6
Python: expand TypeSafe sample guidance
eavanvalkenburg Sep 21, 2026
2220be1
Python: link TypeSafe sample documentation
eavanvalkenburg Sep 21, 2026
5ea4fbd
Python: address TypeSafe review findings
eavanvalkenburg Sep 22, 2026
b60a7be
Python: preserve TypeSafe safety constraints
eavanvalkenburg Sep 22, 2026
d932ce7
Python: add TypeSafe security and judge samples
eavanvalkenburg Sep 23, 2026
80b1b2d
Python: support TypeSafe loop judge directly
eavanvalkenburg Sep 23, 2026
55be075
Python: support TypeSafe quarantine directly
eavanvalkenburg Sep 23, 2026
c67b22d
Python: log TypeSafe judge exchanges
eavanvalkenburg Sep 23, 2026
c21482d
Python: clarify TypeSafe judge logging
eavanvalkenburg Sep 23, 2026
43efdf7
Python: support provider-specific loop judges
eavanvalkenburg Sep 23, 2026
c5efe92
Merge remote-tracking branch 'upstream/main' into add-jev-structured-…
eavanvalkenburg Sep 23, 2026
9c328e8
Python: harden TypeSafe tool schema limits
eavanvalkenburg Sep 24, 2026
0b4a6bf
Merge commit '9c328e80cacba89aacb942e9a9a4e28075cc9f24' into add-jev-…
eavanvalkenburg Sep 24, 2026
a21cf24
Python: cap TypeSafe question materialization
eavanvalkenburg Sep 24, 2026
3079cd7
Python: authenticate TypeSafe transport
eavanvalkenburg Sep 24, 2026
6e164ce
Merge commit '3079cd7cb282a21da4db75892e41450cca5fef9b' into add-jev-…
eavanvalkenburg Sep 24, 2026
0170ede
Python: validate TypeSafe array enums
eavanvalkenburg Sep 25, 2026
2af91ed
Python: preserve TypeSafe schema semantics
eavanvalkenburg Sep 25, 2026
483f377
Merge commit '2af91ed270306da020ccf25b7e26bdd44c13a9b5' into add-jev-…
eavanvalkenburg Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/.cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
"ndarray",
"nopep",
"NOSQL",
"noul",
"nouls",
"ollama",
"Onnx",
"onyourdatatest",
Expand Down
32 changes: 32 additions & 0 deletions python/CODING_STANDARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,38 @@ This is not a strict rule, but a guideline to help maintain consistency across t

## Implementation Decisions

### Settings Resolution in Connectors

Connector constructors should use `load_settings` to resolve explicit keyword
arguments, an explicitly selected `.env` file, process environment variables,
and defaults before deciding that a required value is missing.

When a preconfigured SDK client can be injected, both the client and individual
settings may be omitted at the constructor boundary. Make settings required only
for the branch that creates the SDK client:

```python
settings = load_settings(
ProviderSettings,
env_prefix="PROVIDER_",
required_fields=[] if client is not None else ["api_key"],
api_key=api_key,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)

if client is None:
client = ProviderClient(
api_key=cast(SecretString, settings.get("api_key")).get_secret_value(),
)
```

Do not prevalidate optional constructor arguments before settings resolution.
In particular, do not reject an injected client merely because an optional
credential or model argument was also supplied. The injected client owns its
transport and authentication; resolved settings are used only when constructing
a client.

### Asynchronous Programming

It's important to note that most of this library is written with asynchronous in mind. The
Expand Down
1 change: 1 addition & 0 deletions python/PACKAGE_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Status is grouped into these buckets:
| `agent-framework-qdrant` | `python/packages/qdrant` | `alpha` |
| `agent-framework-redis` | `python/packages/redis` | `beta` |
| `agent-framework-tools` | `python/packages/tools` | `beta` |
| `agent-framework-typesafe` | `python/packages/typesafe` | `alpha` |

## Deprecated / removed packages

Expand Down
2 changes: 1 addition & 1 deletion python/packages/core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ The vector store API is experimental under the shared `VECTOR_STORES` feature ID
declaration remains unmatched; genuinely ambiguous duplicate declarations stay separate.
### Agent Loop (`_harness/_loop.py`)

- **`AgentLoopMiddleware`** - `AgentMiddleware` that re-runs an agent in a loop by calling `call_next()` repeatedly (the pipeline re-reads `context.messages` each time). One configurable class covers two patterns: a required user `should_continue` predicate (sync or async, the first positional/keyword arg), and a chat-client judge built via the `.with_judge(...)` factory (a second chat client decides whether the original request was answered; loops while it is *not*, using a `JudgeVerdict` structured-output response — internally just an async `should_continue` predicate). The constructor covers the predicate pattern directly; only the judge has a convenience classmethod factory (`.with_judge(judge_client, ...)`) that forwards to `__init__`. Supports both streaming and non-streaming runs. By default a non-streaming run returns an aggregated `AgentResponse` containing every iteration's messages plus the injected `next_message` "nudge" messages (as `user` messages); set `return_final_only=True` to return only the last iteration's response. Streaming runs always yield each iteration's updates and emit the injected nudge messages as `user` updates between iterations (the `return_final_only` flag has no effect on streaming, and the final response reflects the last iteration; `MiddlewareTermination` is handled cleanly). `should_continue` is required; other constructor args are optional: `max_iterations` (safety cap; defaults to `DEFAULT_MAX_ITERATIONS`=10, explicit `None`→unbounded, positive int caps; `.with_judge` uses `DEFAULT_JUDGE_MAX_ITERATIONS`=5 as its default), `next_message` (defaults to a short "continue" nudge), `return_final_only`, and `additional_instructions` (an extra `system` message injected ahead of the input before the agent runs — becomes part of the original messages so it survives `fresh_context` resets and persists via a session). The judge is configured only through `.with_judge` (`judge_client`/`instructions`/`criteria`), not the constructor, and its `reasoning` is fed back to the agent as the next iteration's input; the judge forwards the original request messages and the agent's latest response messages verbatim so multi-modal content is preserved. `criteria` (a `list[str]`) is both injected as the agent's `additional_instructions` and rendered into the judge instructions wherever the `{{criteria}}` placeholder (`CRITERIA_PLACEHOLDER`) appears (`DEFAULT_JUDGE_INSTRUCTIONS` ends with it; custom `instructions` may include it, and it is stripped when no criteria are given). The `should_continue`/`next_message` callables are invoked with keyword args (`iteration`, `last_result`, `messages`, `original_messages`, `session`, `agent`, `progress`, `feedback`) and may be sync or async; declare only what you need plus `**kwargs`. `should_continue` may return a plain `bool` or a `(bool, str | None)` tuple whose second item is feedback surfaced to `next_message`/`record_feedback` via the `feedback` kwarg (the judge uses this to relay its `reasoning`). Stop precedence per iteration is `max_iterations` → `should_continue`, evaluated before `record_feedback` so the feedback is available to it.
- **`AgentLoopMiddleware`** - `AgentMiddleware` that re-runs an agent in a loop by calling `call_next()` repeatedly (the pipeline re-reads `context.messages` each time). One configurable class covers two patterns: a required user `should_continue` predicate (sync or async, the first positional/keyword arg), and a chat-client judge built via the `.with_judge(...)` factory (a second chat client decides whether the original request was answered; loops while it is *not*, using a `JudgeVerdict` structured-output response by default — internally just an async `should_continue` predicate). Provider-specific judges pass their structured format through `response_format` and use a sync or async `verdict_parser` to convert the full `ChatResponse` into `JudgeVerdict`; parser errors and invalid return values surface without falling back to text markers. The constructor covers the predicate pattern directly; only the judge has a convenience classmethod factory (`.with_judge(judge_client, ...)`) that forwards to `__init__`. Supports both streaming and non-streaming runs. By default a non-streaming run returns an aggregated `AgentResponse` containing every iteration's messages plus the injected `next_message` "nudge" messages (as `user` messages); set `return_final_only=True` to return only the last iteration's response. Streaming runs always yield each iteration's updates and emit the injected nudge messages as `user` updates between iterations (the `return_final_only` flag has no effect on streaming, and the final response reflects the last iteration; `MiddlewareTermination` is handled cleanly). `should_continue` is required; other constructor args are optional: `max_iterations` (safety cap; defaults to `DEFAULT_MAX_ITERATIONS`=10, explicit `None`→unbounded, positive int caps; `.with_judge` uses `DEFAULT_JUDGE_MAX_ITERATIONS`=5 as its default), `next_message` (defaults to a short "continue" nudge), `return_final_only`, and `additional_instructions` (an extra `system` message injected ahead of the input before the agent runs — becomes part of the original messages so it survives `fresh_context` resets and persists via a session). The judge is configured only through `.with_judge` (`judge_client`/`instructions`/`criteria`/`response_format`/`verdict_parser`), not the constructor, and its `reasoning` is fed back to the agent as the next iteration's input; the judge forwards the original request messages and the agent's latest response messages verbatim so multi-modal content is preserved. `criteria` (a `list[str]`) is both injected as the agent's `additional_instructions` and rendered into the judge instructions wherever the `{{criteria}}` placeholder (`CRITERIA_PLACEHOLDER`) appears (`DEFAULT_JUDGE_INSTRUCTIONS` ends with it; custom `instructions` may include it, and it is stripped when no criteria are given). The `should_continue`/`next_message` callables are invoked with keyword args (`iteration`, `last_result`, `messages`, `original_messages`, `session`, `agent`, `progress`, `feedback`) and may be sync or async; declare only what you need plus `**kwargs`. `should_continue` may return a plain `bool` or a `(bool, str | None)` tuple whose second item is feedback surfaced to `next_message`/`record_feedback` via the `feedback` kwarg (the judge uses this to relay its `reasoning`). Stop precedence per iteration is `max_iterations` → `should_continue`, evaluated before `record_feedback` so the feedback is available to it.
- **Feedback tracking** - `record_feedback` captures a per-iteration progress entry (called with the loop kwargs; if it returns a truthy string the entry is appended, otherwise the agent's response text is used as the fallback entry). The accumulated log is exposed to every callback via the `progress` keyword (a per-iteration copy of prior entries) and, when `inject_progress=True` (default), injected into the next iteration's input as a `user` message (the full log without a session, only the latest entry with a session to avoid duplicating history). `fresh_context=True` restarts each iteration from the original task plus the progress log; when a session is attached it is snapshotted (`to_dict()`) before the loop and restored (`from_dict` + field copy) between iterations so the local transcript and any service-side conversation id reset too (in-loop working-state is discarded, pre-loop state preserved, continuity carried only by the progress log).
- **`todos_remaining(*, looping_modes=None)`** / **`todos_remaining_message`** - Helper factories for todo-driven loops (the Python counterpart of .NET's `TodoCompletionLoopEvaluator`), designed for `create_harness_agent` but usable with any agent that registers a `TodoProvider` via `context_providers`. They resolve the `TodoProvider`/`AgentModeProvider` from the *running agent* (`agent.context_providers`, via `_resolve_context_provider`) rather than taking the provider as an argument, so they can be wired directly into `loop_should_continue`/`loop_next_message`. `todos_remaining` returns a `should_continue` predicate that loops while any todo is open; pass `looping_modes=[...]` to gate looping to specific operating modes (case-insensitive; honors the `AgentModeProvider`'s `source_id`/`available_modes`), `looping_modes=None` (default) applies in every mode, and an empty sequence raises `ValueError`. `todos_remaining_message` is a `next_message` callable that lists the still-open todo titles and tells the agent to finish them, returning `None` when the session/agent/provider is unavailable or nothing is open (in which case the middleware's default `None` handling applies: reuse the previous iteration's messages verbatim under the default `fresh_context=False`, or `DEFAULT_NEXT_MESSAGE` only when `fresh_context=True`).
- **`background_tasks_running()`** / **`background_tasks_running_message`** - Helper factories for background-agent-driven loops, mirroring the `todos_remaining` pair. They resolve the `BackgroundAgentsProvider` from the *running agent* (`agent.context_providers`, via `_resolve_context_provider`) rather than taking the provider as an argument, so they can be wired directly into `create_harness_agent`'s `loop_should_continue`/`loop_next_message`. `background_tasks_running` returns a `should_continue` predicate that loops while the provider's persisted state shows any task with `status == RUNNING` (pair it with `max_iterations` so the loop is bounded even if a task's persisted status is never refreshed). `background_tasks_running_message` is a `next_message` callable that lists the still-running tasks (`#<id> (<agent_name>): <description>`) and tells the agent to wait for them to finish and retrieve their results, returning `None` when the session/agent/provider is unavailable or no task is running.
Expand Down
Loading
Loading