mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 11:52:25 +02:00
* refactor(skills): extract evaluation and audit orchestration * chore(security): scope OpenAPI digest Gitleaks exception * refactor(skills): establish domain-oriented service boundaries * refactor(skills): address architecture review feedback * fix(skills): restore prompt seams and harden signature contracts * test(skills): restore ReDoS coverage and simplify API baseline
380 lines
22 KiB
Markdown
380 lines
22 KiB
Markdown
# Skills evaluation and audit architecture
|
|
|
|
The Skills backend separates HTTP transport, model evaluation, agent execution,
|
|
audit orchestration and publication policy. `services/skills/` coordinates those
|
|
responsibilities; `services/memory/skills.py` remains the canonical persistence
|
|
and lifecycle owner. The extraction preserves the existing HTTP and Python
|
|
contracts. [PR #6711](https://github.com/odysseus-dev/odysseus/pull/6711) tracks
|
|
this boundary change under [issue #6710](https://github.com/odysseus-dev/odysseus/issues/6710).
|
|
|
|
## Responsibilities and dependency boundaries
|
|
|
|
| Module | Responsibility |
|
|
| --- | --- |
|
|
| `routes/skills_routes.py` | FastAPI request models, endpoint declarations, authentication, ownership, HTTP errors, exact approval validation and Python compatibility bindings |
|
|
| `services/skills/job_coordinator.py` | Transient job creation, continuation after approval validation, candidate selection, public polling projections and cancellation requests |
|
|
| `services/skills/agent_runner.py` | Manual execution, audit arms, agent loop integration, transcripts, authority propagation, approval pauses and same-task stream closure |
|
|
| `services/skills/audit_workflow.py` | Per-skill audit sequencing, self-edit retries, teacher escalation, sequential batches and scheduled execution |
|
|
| `services/skills/evaluation.py` | Model grading, verdict parsing, necessity and retrieval checks, efficiency fields and markdown improvement generation |
|
|
| `services/skills/publication.py` | Confidence and publishing policy, duplicate rules, batch finalization and markdown updates through SkillsManager |
|
|
| `services/skills/model_selection.py` | Owner-scoped worker/teacher resolution, utility fallback, endpoint selection and credential lookup |
|
|
| `services/skills/prompts.py` | Shared task construction and explicitly untrusted skill context |
|
|
| `services/skills/dependencies.py` | Frozen `AuditDependencies` containing callable bindings and transient registry references |
|
|
| `services/memory/skills.py` | Canonical skill identity, owner isolation, durable storage, indexing, retrieval and lifecycle state |
|
|
|
|
The package initializer identifies the domain without adding a service facade.
|
|
Evaluation does not schedule jobs or persist skills. Publication computes policy
|
|
and applies updates through the supplied SkillsManager. Services do not import
|
|
`routes.skills_routes`. The existing preferences adapter in `publication.py`
|
|
still imports `routes.prefs_routes`; moving that adapter is a separate change.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
HTTP[Authenticated HTTP requests] --> Routes[skills_routes]
|
|
Builtins[builtin_actions callers] --> Routes
|
|
Routes --> Jobs[job_coordinator]
|
|
Routes --> Models[model_selection]
|
|
Routes --> Evaluation[evaluation]
|
|
Routes --> Runner[agent_runner]
|
|
Routes --> Workflow[audit_workflow]
|
|
Jobs -->|injected calls| Runner
|
|
Jobs -->|injected calls| Workflow
|
|
Workflow -->|injected calls| Runner
|
|
Workflow -->|injected calls| Evaluation
|
|
Workflow -->|injected calls| Publication[publication]
|
|
Runner -->|injected calls| Evaluation
|
|
Runner --> Runtime[Agent runtime and authority enforcement]
|
|
Runner --> Prompts[prompts and untrusted context]
|
|
Jobs --> Prompts
|
|
Workflow --> Prompts
|
|
Routes --> Manager[Canonical SkillsManager]
|
|
Runner --> Manager
|
|
Workflow --> Manager
|
|
Publication --> Manager
|
|
Publication --> Preferences[Existing preferences adapter]
|
|
Models --> Resolver[Endpoint resolver and owner-scoped credentials]
|
|
```
|
|
|
|
Arrows include injected calls, rather than only Python imports. The workflow
|
|
imports `SkillAuditUnavailable` from the runner. Route wrappers construct
|
|
`AuditDependencies` on entry from the current route bindings, so existing
|
|
callers and monkeypatches reach the same collaborators without rebinding shared
|
|
service globals.
|
|
|
|
The manual test registry belongs to `agent_runner.py`; the audit registry belongs
|
|
to `audit_workflow.py`. Routes expose aliases to those dictionaries. Registries
|
|
hold process-local job state and are not durable skill stores. All durable
|
|
metadata, confidence, markdown and publication changes use SkillsManager.
|
|
Published-only retrieval and local exclusion of `remote-only` skills stay in
|
|
the manager/index boundary. Service loggers use their module names; the route
|
|
module retains its own logger. Root handlers supply the existing log output.
|
|
|
|
## Execution and publication lifecycle
|
|
|
|
1. The HTTP handler authenticates the caller, verifies ownership and validates
|
|
the request. Manual model choices retain registered-endpoint validation and
|
|
owner-scoped credential lookup.
|
|
2. Job coordination creates the owner-keyed transient record and launches the
|
|
execution coroutine. Replacing a manual test retires its pending approval.
|
|
Polling exposes only the existing public fields, excluding credentials,
|
|
private transcripts, continuation state and asyncio tasks.
|
|
3. The runner uses the originating request authority or the consumed sealed
|
|
approval authority. It captures the transcript and closes the async stream
|
|
with `aclosing` inside the task that opened it, including early approval
|
|
pauses, cancellation and exceptions.
|
|
4. Manual approval pauses return to the HTTP validation boundary. Continuation
|
|
reconstructs context from the original untrusted skill and accumulated
|
|
untrusted transcript. Manual completion records audit metadata and adjusts
|
|
confidence (pass .95, needs_work .6, fail .4) without publishing.
|
|
5. An automated audit checks deterministic duplicates, advisory necessity and,
|
|
when indicated, retrieval precision. It runs skill and no-skill arms on the
|
|
same task, grades the result, then may self-edit and retry. An optional
|
|
distinct teacher rewrites the skill; the worker performs the retry.
|
|
6. Publication applies owner preferences, confidence thresholds, necessity,
|
|
utility and duplicate rules through SkillsManager. A normal sequential batch
|
|
finalizes its processed results. Cancellation or infrastructure unavailability
|
|
skips batch finalization and clears task/current state.
|
|
|
|
A clean audit pass sets confidence to .95, a self-edit pass to .85 and a teacher
|
|
rewrite pass to .8. Continued failure becomes draft at .35. Unknown and
|
|
inconclusive verdicts preserve publication state. Automatic publishing requires
|
|
an enabled owner policy, a passing verdict at or above the configured threshold,
|
|
and no necessity, worse-baseline or duplicate blocker. The default threshold is
|
|
.85; owner values are clamped to [0, 1]. Markdown edits pin the existing skill name
|
|
so usage and audit sidecars remain attached to the same identity.
|
|
|
|
Per-skill duplicate blocking and batch duplicate finalization have distinct
|
|
selection rules and must not be consolidated without a behavior proposal.
|
|
Batch finalization excludes builtins and binned records, changes only processed
|
|
skills, protects approval-required results, and bins duplicate losers. Duplicate
|
|
similarity uses the existing .38 threshold and name-suffix grouping.
|
|
|
|
Batches run sequentially per owner, retain current/done/results and timestamps,
|
|
and cap progress logs at 1,000 entries. Ordinary exceptions record inconclusive
|
|
skill audits. Infrastructure failures stop the batch without inventing a skill
|
|
verdict. Scheduled execution skips an active owner job, selects existing lifecycle
|
|
candidates by oldest audit, caps the batch (default eight), and marks model phases
|
|
as background work. These services retain the existing scheduling and
|
|
cancellation semantics.
|
|
|
|
## Security and approval invariants
|
|
|
|
HTTP authentication and owner verification remain at the transport boundary.
|
|
Manager reads and writes receive the owner. Ownerless builtins retain their
|
|
existing visibility; an authenticated user cannot access an ownerless ordinary
|
|
skill. Existing ownerless deployment behavior is preserved.
|
|
|
|
The `/test-approval` handler validates the pending job, approval ID, decision,
|
|
owner and empty session binding, then destructively consumes the sealed approval
|
|
with `allow_continuation=False` before calling the coordinator. Stale, mismatched
|
|
or reused approvals fail with 409. The consumed grant authorizes only its exact
|
|
action. Services do not manufacture approval authority or relax process identity,
|
|
workspace confinement or request-authority enforcement in the agent runtime.
|
|
|
|
Skill markdown and resumed transcripts remain explicitly untrusted context,
|
|
never trusted system instructions. Audit arms suppress saved skill injection.
|
|
Unattended runs deny encountered approval records instead of leaving reusable
|
|
grants. An initial approval-required audit result preserves the skill's publication
|
|
and confidence and directs the user to manual approval. Retry handling has an
|
|
inherited gap: an approval pause after a rewrite can still lead to demotion.
|
|
Same-task stream closure preserves the originating ContextVar cleanup, but manual
|
|
cancellation can retain private continuation state. These are separate hardening
|
|
items, not guarantees supplied by the extraction.
|
|
|
|
## Model selection and evaluation
|
|
|
|
Audit resolution uses an explicit endpoint/model pair when supplied, looking up
|
|
credentials among enabled owner-visible endpoints. A model-only choice uses the
|
|
existing owner-scoped model resolver. Otherwise it resolves the utility model,
|
|
including the resolver's default behavior, then takes the first configured
|
|
utility fallback if the primary result is incomplete. A missing worker raises
|
|
`ValueError`. Served-model normalization keeps an exact match, then a basename
|
|
match, then the first served ID; discovery exceptions keep the resolved choice.
|
|
|
|
An enabled teacher resolves separately with the same owner. Its credentials are
|
|
not borrowed from the worker. Teacher resolution failure logs the existing warning
|
|
and leaves the teacher absent. Manual tests retain their HTTP endpoint validation
|
|
and registered fallback credentials.
|
|
|
|
Owner reasoning effort is resolved for each evaluation or rewrite call. Workload
|
|
flags, credentials, retries, prompts and permissive parsing remain unchanged:
|
|
|
|
| Phase | Token budget | Timeout in seconds |
|
|
| --- | --- | --- |
|
|
| Run review, up to two attempts | 32768 | 180 |
|
|
| Necessity | 8192 | 120 |
|
|
| Retrieval precision | 4096 | 90 |
|
|
| Markdown improvement | 16384 | 180 |
|
|
|
|
Review parsing retains think-block stripping, trailing-comma recovery, last
|
|
usable verdict selection, prose fallback, bounded fields and unknown outcomes.
|
|
These model outputs are advisory and do not grant execution authority.
|
|
|
|
## Public HTTP contract
|
|
|
|
All paths below have prefix `/api/skills`. Successful status defaults to 200.
|
|
Request models and endpoint declaration order remain fixed. A frozen
|
|
OpenAPI and declaration-order fixture records
|
|
the baseline contract independently of the extracted implementation.
|
|
|
|
| Method and suffix | Successful response | Authorization and errors |
|
|
| --- | --- | --- |
|
|
| GET empty | `skills,count` | Owner-filtered load |
|
|
| GET `/index` | `index,count` | Owner-scoped eligible index |
|
|
| GET `/slash-catalog` | `skills,count` | Owner-scoped index; sorted slash entries |
|
|
| GET `/builtin` | `builtin,count` and optional `error` | Existing shared capability text |
|
|
| GET `/builtin/{name}` | `name,text,default,is_overridden` | 404 missing tool; 500 import failure |
|
|
| PUT `/builtin/{name}` | `ok,name,is_overridden` | Admin; 404 unknown, 400 missing text |
|
|
| DELETE `/builtin/{name}` | `ok,name,is_overridden` | Admin |
|
|
| POST `/import-from-url` | `ok,skill,files` | Admin and current owner; 400 import, 502 HTTP, 500 other failure |
|
|
| POST `/add` | `ok,deduped,skill` | Current owner passed to manager; unchanged old/new request fields |
|
|
| POST `/{skill_id}/invoke` | `ok,type,name,command,message` | Eligible owner index only; 404 unavailable/source missing |
|
|
| GET `/{skill_id}` | Full skill dictionary | Owner load; 404 missing |
|
|
| GET `/{skill_id}/markdown` | `name,markdown` | Owner verification; 404 missing/source missing |
|
|
| POST `/{skill_id}/test` | `ok,status,skill,model` | Owner verification, HTTP request authority, registered fallback credentials; 400 missing/invalid model |
|
|
| POST `/{skill_id}/test-approval` | `ok,status,decision` | User approval request, owner verification, pending ID/owner/empty session match; 400 invalid body/decision, 409 stale/mismatch/consumed |
|
|
| GET `/{skill_id}/test-status` | `status` or `status,task,model,log,verdict,approval` | Owner-keyed lookup; server continuation/credentials omitted |
|
|
| POST `/audit-all` | `ok,status,total` plus running `done,model` or empty `results,log` | Owner-keyed singleton; 400 model resolution |
|
|
| GET `/audit-all/status` | `status` or job progress/result/log/timestamps | Owner-keyed lookup; task and credentials omitted |
|
|
| POST `/audit-all/cancel` | `ok,status` | Owner-keyed lookup and cancellation |
|
|
| POST `/{skill_id}/markdown` | `ok,name` | Owner verification; 400 missing/parse, 404 missing, 500 update |
|
|
| PUT `/{skill_id}` | `ok` | Owner verification and scoped manager update; 404 missing |
|
|
| DELETE `/{skill_id}` | `ok` | Owner verification and scoped manager deletion; 404 missing |
|
|
| POST `/search` | `skills,query,count` | Owner-filtered input; 400 empty query |
|
|
|
|
Strict owner verification allows an ownerless builtin but denies an ownerless
|
|
ordinary skill for an authenticated user. The existing ownerless deployment
|
|
behavior remains. Global auth middleware is unchanged.
|
|
|
|
## Python compatibility and frozen contracts
|
|
|
|
`app.py` imports `setup_skills_routes`. `src/builtin_actions.py` uses the existing
|
|
route helpers and audit registry. Route aliases and wrappers preserve the original
|
|
28 inspectable interfaces, `SkillAuditUnavailable`, registry identity and patchable
|
|
collaborators. Lazy model/agent imports and route-level effort injection keep
|
|
existing offline test seams effective.
|
|
|
|
The task and message callbacks are bound through `AuditDependencies` at service
|
|
entry. Patches to route-level test tasks and test/baseline messages reach manual
|
|
execution, approval continuation, both audit arms, worker retries and scheduled
|
|
audits. The `_skill_baseline_task` compatibility alias delegates to the current
|
|
route test-task builder. Execution continues to reuse one task for both arms,
|
|
as in the original implementation; it does not independently derive a baseline
|
|
task through that alias.
|
|
|
|
`tests/fixtures/skills_api_baseline.json` records declaration order and structural
|
|
signatures captured from pre-extraction revision
|
|
`86b886c611338282af6d4a717246ea4e256f58df`. Signature records retain parameter
|
|
names, order, kinds, defaults, default factories and annotations, including
|
|
Pydantic constraint metadata. They compare typing origins and arguments instead
|
|
of Python-version-dependent annotation representations. The expanded records also
|
|
pin field validation aliases, alias priority, discriminators, `validate_default`,
|
|
rebuilt field annotations and model configuration. Field order remains visible
|
|
through the constructor parameter sequence. Only the known builtin factories
|
|
`list`, `tuple`, `dict`, `set` and `frozenset` are supported. Custom factories,
|
|
including closures and factories taking validated data, fail closed without being
|
|
executed. Unsupported metadata/configuration values also raise rather than using
|
|
unstable representations. This is a structural contract for supported interfaces,
|
|
not proof of behavioral equivalence for arbitrary validators or Python code.
|
|
|
|
The expanded fixture was derived again from the original revision above in a
|
|
detached scratch worktree, with the updated comparator copied in. Python 3.11
|
|
and 3.12 produced byte-identical snapshots. The previous parameter/return records,
|
|
all 28 interface names, all 22 endpoint declarations and the original OpenAPI
|
|
digest were checked unchanged before accepting the new field records.
|
|
|
|
`tests/fixtures/skills_openapi_baseline.json` is the same baseline's complete
|
|
Skills-router OpenAPI document, generated with a standalone FastAPI application.
|
|
Sorted keys, ASCII escaping, two-space indentation and a final newline make the
|
|
fixture deterministic. Tests require exact canonical equality and print a unified
|
|
diff on mismatch. The redundant digest field and assertions have been removed;
|
|
they provided no independent compatibility guarantee. The fixture pins model
|
|
schemas and constraints. CI reads committed fixtures and needs no Git history
|
|
for these tests. Baseline updates require an intentional compatibility decision, rather
|
|
than regenerating fixtures from a failing implementation.
|
|
|
|
The narrow `.gitleaks.toml` exception remains necessary for the historical public
|
|
digest in earlier fixture and documentation revisions. It applies to the exact digest,
|
|
the `generic-api-key` rule and the two named paths. Removing a current copy cannot
|
|
remove findings from published history. Keep the exception unless the pinned
|
|
full-history scan proves it unnecessary; do not rewrite history to remove it.
|
|
The checksum-verified 8.30.1 scan without this exception still reports both
|
|
historical copies in commit `a547c4d8`; retaining the exception passes the full
|
|
PR/dev history scan.
|
|
Reconsider it after squash merge only after verifying the resulting reachable
|
|
history with the pinned full-history scan.
|
|
|
|
The ReDoS source guard scans `services/skills/evaluation.py`, the canonical
|
|
evaluation parser module, as well as the retained route and memory-extractor
|
|
locations. Verdict regex runtime tests import the canonical module directly.
|
|
The source guard must reject reintroducing the lazy think-block expression;
|
|
the ordinary-input and adversarial-flood runtime tests remain in place. An isolated
|
|
source mutation reproduced the old guard's false green for the evaluation module.
|
|
The corrected guard rejected the vulnerable expression in each of the three
|
|
locations and passed again after restoring the temporary copies. The mutated
|
|
regex was never executed, and production parser behavior was not changed.
|
|
|
|
## Reproducible validation
|
|
|
|
From the repository root, use a virtual environment with `requirements-dev.txt`
|
|
installed and the repository's frontend dependencies available. The examples use
|
|
`.venv/bin/python`; repeat the contract tests with a Python 3.12 environment as
|
|
well as Python 3.11. Record interpreter/dependency versions with results.
|
|
|
|
```sh
|
|
.venv/bin/python -m pytest -q \
|
|
tests/test_skills_audit_characterization.py tests/test_skills_audit_services.py
|
|
.venv/bin/python -m compileall -q app.py core routes src services scripts tests
|
|
.venv/bin/python -c 'import routes.skills_routes; import services.skills.agent_runner; import services.skills.audit_workflow'
|
|
.venv/bin/python scripts/build_frontend.py --check
|
|
node scripts/check_frontend.mjs
|
|
node --test tests/skillsApproval.test.mjs tests/frontendArchitecture.test.mjs
|
|
git diff --check
|
|
```
|
|
|
|
The focused Skills, authority and approval selection is reproducible with:
|
|
|
|
```sh
|
|
.venv/bin/python - <<'PYTHON'
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
files = set()
|
|
for path in Path("tests").rglob("test_*.py"):
|
|
text = path.read_text()
|
|
if ("skill" in path.name or "approval" in path.name
|
|
or "utility_reasoning" in path.name or "skills_routes" in text
|
|
or "review_endpoint_credentials" in path.name):
|
|
files.add(str(path))
|
|
files.update([
|
|
"tests/test_request_authority.py",
|
|
"tests/test_endpoint_registered_authority.py",
|
|
"tests/test_redos_verdict_continuation.py",
|
|
"tests/test_redos_residual_python.py",
|
|
])
|
|
subprocess.run([sys.executable, "-m", "pytest", "-q", "-rs",
|
|
"--durations=15", *sorted(files)], check=True)
|
|
PYTHON
|
|
```
|
|
|
|
The original selection contained 729 cases. Additional characterization cases
|
|
increase that count without removing the original coverage. For the full suite,
|
|
remove a development `APP_PORT` and put the environment's tools on PATH:
|
|
|
|
```sh
|
|
env -u APP_PORT PATH="$PWD/.venv/bin:$PATH" \
|
|
PLAYWRIGHT_BROWSERS_PATH="$PWD/.cache/ms-playwright" \
|
|
.venv/bin/python -m pytest -q -rs --durations=25
|
|
```
|
|
|
|
Run the checksum-verified Gitleaks binary pinned in
|
|
`.github/workflows/secret-scan.yml` on full reachable history, including the PR
|
|
head and fetched official `dev`. The current workflow pins 8.30.1. Its CI command
|
|
is `gitleaks git --no-banner --redact --verbose .` with a full-history checkout.
|
|
A local checkout can restrict refs to the relevant histories with
|
|
`--log-opts='HEAD upstream/dev'`; do not restrict the commit range to just the
|
|
latest patch. Scan uncommitted fixtures too before committing.
|
|
|
|
## Historical evidence and validation limits
|
|
|
|
The initial extraction validation at `a547c4d8` reported 42 characterization and
|
|
service cases, 729 focused cases, 13 relevant Node cases and 17 isolated uvicorn
|
|
HTTP checks passing. The historical repository-wide run reported 13,959 passed,
|
|
60 skipped and three failures. These counts describe the extraction validation,
|
|
not the current revision. The HTTP checks used isolated state and authentication
|
|
and included second-owner denial, but exercised neither a configured live model
|
|
nor an external tool action.
|
|
|
|
The two containment failures reproduced on unchanged baseline `86b886c6` with
|
|
extra `Failed to create stream fd: No such file or directory` stderr output:
|
|
|
|
- `test_namespace_handshake_closes_model_stdin`
|
|
- `test_execution_environment_cannot_replace_probed_bwrap[False]`
|
|
|
|
`test_legacy_cleanup_against_a_private_real_tmux_server` failed under the original
|
|
full-suite load and passed isolated reruns. Its cause was not fully diagnosed.
|
|
These historical failures must be distinguished from new regressions; successful
|
|
CI and maintainer review remain integration gates. Earlier AST comparisons and
|
|
the [maintainer review](https://github.com/odysseus-dev/odysseus/pull/6711#issuecomment-6170115749)
|
|
provide historical extraction evidence, not an independent review of later fixes.
|
|
|
|
## Known limitations and follow-up boundaries
|
|
|
|
[Issue #6635](https://github.com/odysseus-dev/odysseus/issues/6635) remains open:
|
|
manual tests without an appropriate workspace can still fail to execute their
|
|
proposed action. Stream cleanup and exact approvals are preserved, but do not
|
|
supply a missing workspace.
|
|
|
|
Jobs remain process-local and do not survive restarts. Durable jobs, moving the
|
|
preferences adapter below transport, consolidating storage under the domain
|
|
package, or changing duplicate/publication policies need separate designs.
|
|
The canonical manager, skill format/lifecycle modules, provider implementations,
|
|
agent authority enforcement and frontend contracts remain their existing owners.
|
|
|
|
The [second-review correction report](skills-audit-second-review.md) records the
|
|
restored prompt seams, independently expanded constructor baseline, actual
|
|
validation results and a separate prioritized plan for the six inherited P2
|
|
failure paths. Model-controlled lifecycle fields and preference-load failures
|
|
require maintainer action before automatic rewrite/publication deployment.
|