Skip to content

Commit b2bbbe3

Browse files
committed
feat(forward): support state and session decisions
1 parent 742219f commit b2bbbe3

18 files changed

Lines changed: 1480 additions & 1 deletion

‎.env.live.example‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,9 @@ QODER_REALTIME_GLOBAL_SAT=
5656
# Uses QODER_FORWARD_CN_PAT, QODER_FORWARD_GLOBAL_PAT, QODER_SA_CN_KEY,
5757
# and QODER_SA_GLOBAL_KEY. Never point clear at a shared Identity.
5858
QODER_DRIVE_LIVE_ALLOW_WRITE=false
59+
60+
# Forward Decisions consume model usage and retain records until server expiry.
61+
# Also requires QODER_FORWARD_LIVE_ALLOW_WRITE/EXECUTION=true.
62+
QODER_FORWARD_LIVE_ALLOW_DECISIONS=false
63+
# Optional existing Session for the Python/TypeScript live suite and all examples.
64+
QODER_DECISION_SESSION_ID=

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ existing `0.1.0` release; earlier development prereleases are not listed.
55

66
## [Unreleased]
77

8+
### Added
9+
10+
- Add Forward Decisions for synchronous evaluation of explicit state or accessible Session history, with choice and score questions, idempotent creation, retrieval, cursor pagination, and typed context truncation/token-estimate metadata.
11+
812
## [0.9.0] - 2026-10-26
913

1014
### Added

‎CONTRIBUTING.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,26 @@ those tests do not replace SDK integration coverage.
179179
## Realtime validation
180180

181181
Realtime REST and local WebSocket protocol/lifecycle tests run offline with the default suite. `uv run make test-live-realtime` is a separate opt-in CN/Global entry, controlled by the dedicated `QODER_REALTIME_*` configuration and both write/execution flags. It creates persistent Conversations using already-enabled Templates; transport close is not resource cleanup. Record the printed Conversation/request IDs and agree on retention with the service owner. Basic ping/history success does not replace text, PCM, playback, and background Work acceptance. Realtime auth headers, SSL context, and proxy settings use the WebSocket adapter; arbitrary httpx middleware is not applied to it.
182+
183+
## Decision live tests
184+
185+
Decision evaluation is opt-in because it consumes model usage and creates
186+
records without a delete endpoint. Configure a dedicated account using
187+
`QODER_FORWARD_PAT` and `QODER_FORWARD_BASE_URL` in `.env.live`, then set
188+
`QODER_FORWARD_LIVE_ALLOW_WRITE=true`,
189+
`QODER_FORWARD_LIVE_ALLOW_EXECUTION=true`, and
190+
`QODER_FORWARD_LIVE_ALLOW_DECISIONS=true`. These live tests use PAT; offline
191+
contracts cover both PAT and SAT behavior. Normal unit/contract runs are offline.
192+
193+
```bash
194+
QODER_RUN_LIVE=1 uv run pytest tests/integration/test_decisions.py -m integration -v
195+
```
196+
197+
This checks state evaluation, idempotent replay, retrieval, and cursor pages.
198+
Set `QODER_DECISION_SESSION_ID` to an accessible Session with existing history
199+
to also test Session input; otherwise that case is explicitly skipped. The
200+
suite does not modify the supplied Session.
201+
202+
Decision records expire according to server retention. Retain only decision
203+
IDs, HTTP status, and request IDs in shared diagnostics; do not log credentials
204+
or unrelated Session contents.

‎README.md‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,56 @@ credential = client.vaults.credentials.retrieve("credential-id", vault_id="vault
609609
memory = client.memory_stores.memories.retrieve("memory-id", memory_store_id="store-id")
610610
```
611611

612+
## Forward Decisions (next release)
613+
614+
Evaluate a nonempty `state` object or an accessible `session_id` (exactly one)
615+
with 1–32 choice or score questions. Choice values are strings or booleans;
616+
score questions select from your numeric levels. Creation waits for the result.
617+
The service selects the model; no `model` parameter is accepted. Normal PAT or
618+
SAT authentication determines the caller's access.
619+
620+
```python
621+
from uuid import uuid4
622+
from qca import Forward
623+
624+
with Forward() as client: # Or use the PAT/SA configuration above.
625+
decision = client.decisions.create(
626+
state={"message": "My issue is resolved."},
627+
questions=[{
628+
"type": "choice", "name": "resolved",
629+
"instructions": "Has the user confirmed resolution?",
630+
"choices": [{"value": True}, {"value": False}],
631+
}],
632+
idempotency_key=str(uuid4()),
633+
)
634+
stored = client.decisions.retrieve(decision.id)
635+
print(stored.answers)
636+
page = client.decisions.list(limit=20)
637+
print(page.data) # Iterate over page directly to follow all pages.
638+
```
639+
640+
For Session input, replace `state=...` with `session_id=session_id`.
641+
For scores, use `{"type": "score", "instructions": "Rate risk", "levels":
642+
[{"label": "low", "score": 0}, {"label": "high", "score": 10}]}`.
643+
`AsyncForward` offers the same methods with `await` and asynchronous pagination.
644+
645+
Session context is selected by the server. Inspect `context.truncated`,
646+
`context.truncation_reasons`, and the input-token estimate to understand the
647+
selected history. Explicit state and questions are never shortened; oversized
648+
input returns HTTP 422. Context estimates are separate from `usage`, which can
649+
be null. Answers can also have type `refusal`.
650+
651+
Reuse an idempotency key only for identical request bytes. A changed request or
652+
a still-running request with that key returns HTTP 409; the SDK does not retry
653+
that conflict automatically. Creation errors can contain `decision_id` in the
654+
standard API error body. Retrieve and list can return running or failed records.
655+
Lists preserve server order and support `after_id` / `before_id` cursors (one at
656+
a time), optional `session_id`, and a limit of 1–100. There is no delete endpoint;
657+
Decision records expire according to server retention.
658+
659+
See the [runnable example](examples/forward/decisions.py) and the
660+
[opt-in live test instructions](CONTRIBUTING.md#decision-live-tests).
661+
612662
## Forward Drive (Beta)
613663

614664
Drive is a Beta API. It uses `entries` / `next_page_token`, not the ordinary

0 commit comments

Comments
 (0)