harnessx

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package harnessx is the single choke point every SEC-AF agent uses to call the AgentField harness for structured output.

It replaces two things the Python source does implicitly:

  • `app.harness(prompt=..., schema=SomeModel, cwd=..., project_dir=...)` — the pydantic model is resolved here into the JSON-schema map the Go SDK consumes, by GO TYPE NAME (see SchemaFor);
  • `extract_harness_result(result, SomeModel, "AgentName")` from src/sec_af/agents/_utils.py — ported byte-for-byte as Extract, including the diagnostic block it prints to stdout.

RunExtract combines both, because `result = await app.harness(...)` followed immediately by `extract_harness_result(result, Model, name)` is what every single agent module in src/sec_af/agents does.

Index

Constants

This section is empty.

Variables

View Source
var Diagnostics io.Writer = io.Discard

Diagnostics is where Extract writes the diagnostic block Python printed to stdout for a failed or unparseable harness run. It is io.Discard by default: inside codeaf stdout is the terminal the person is looking at, so the lines go nowhere unless the codeaf side points this at a log. Set it before any audit runs; it is read concurrently by every agent.

Functions

func Extract

func Extract[T any](res *appx.HarnessResult, dest *T, agentName string) (T, error)

Extract ports extract_harness_result from src/sec_af/agents/_utils.py exactly:

def extract_harness_result(result, schema, agent_name):
    is_error = bool(getattr(result, "is_error", False))
    if is_error:
        ...
        print(f"[{agent_name}] HARNESS ERROR: {error_message}\n"
              f"  turns={num_turns}, duration_ms={duration_ms}\n"
              f"  result_text={str(result_text)[:500] if result_text else None}",
              flush=True)
        raise RuntimeError(f"{agent_name} harness error: {error_message}")
    parsed = getattr(result, "parsed", None)
    if isinstance(parsed, schema):
        return parsed
    debug_message = (...)
    if isinstance(parsed, dict):
        try: return schema.model_validate(parsed)
        except Exception: print(debug_message, flush=True); raise
    print(debug_message, flush=True)
    raise TypeError(f"{agent_name} did not return a valid {schema.__name__}")

dest is the pointer Run handed to app.Harness. An appx.Harnesser sets HarnessResult.Parsed to that same pointer once it holds a schema-valid object (the SDK runner this seam used to drive did exactly that), so a non-nil Parsed means "*dest is populated and schema-valid" — the exact condition `isinstance(parsed, schema)` tests in Python.

Python parity notes:

  • The diagnostics are reproduced verbatim (written to Diagnostics rather than stdout), including the `result_text=None` spelling that Python's `... if result_text else None` produces for an EMPTY result string (falsy, not just missing), and the 500-character (not byte) truncation of Python's str slice.
  • `type(result).__name__` in the debug line is the Python SDK's `HarnessResult`; Go's concrete type is `appx.HarnessResult`, so the line prints "Result". The line is diagnostic only.
  • The `isinstance(parsed, dict)` branch is Python duck-typing for a SDK that may hand back a raw dict. The Go SDK's Parsed is always the `dest` pointer it was given, so that branch is unreachable here and is deliberately not ported.

func FieldOrders

func FieldOrders(t reflect.Type) map[string][]string

FieldOrders returns, for t and every struct type reachable from it, the ordered json field names of that struct keyed by its Go type name. Anonymous (embedded) structs are flattened, `json:"-"` fields are dropped, and a field with no json tag keeps its Go name — the same rules encoding/json applies.

func FieldOrdersFor

func FieldOrdersFor[T any]() map[string][]string

FieldOrdersFor is FieldOrders for a type parameter.

func FixtureNames

func FixtureNames() []string

FixtureNames lists every committed schema fixture basename, sorted.

func JSONFieldNames

func JSONFieldNames(t reflect.Type) []string

JSONFieldNames returns t's json field names in declaration order.

func LoadFixture

func LoadFixture(name string) (map[string]any, error)

LoadFixture decodes the committed pydantic schema fixture with the given basename (== the pydantic class name). Exported for tests and for tooling that needs a schema without a Go type in hand.

func Run

func Run[T any](ctx context.Context, app appx.Harnesser, prompt string, opts appx.HarnessOptions) (*T, *appx.HarnessResult, error)

Run is the Go form of

result = await app.harness(prompt=prompt, schema=Model, cwd=..., project_dir=...)

It resolves Model's schema by Go type name (SchemaFor), allocates the destination, and calls app.Harness.

The returned error is NON-NIL ONLY when the run could not be made at all. A harness that ran and failed (the agent gave up, unparseable output, schema validation exhausted) comes back as a nil error with HarnessResult.IsError set, exactly as the Python SDK returned a HarnessResult with is_error=True rather than raising; appx.HarnessResult documents that contract for the codeaf implementation. Turning that into an error is Extract's job, so the diagnostic block is written in one place.

*T is always non-nil so callers can hand it straight to Extract; it holds the harness-populated value when HarnessResult.Parsed is set and a default-seeded value otherwise.

func RunExtract

func RunExtract[T any](ctx context.Context, app appx.Harnesser, prompt string, opts appx.HarnessOptions, agentName string) (T, error)

RunExtract is the shape every SEC-AF agent module actually uses:

result = await app.harness(prompt=prompt, schema=Model, cwd=harness_cwd, project_dir=repo_path)
return extract_harness_result(result, Model, "AgentName")

A transport error from app.Harness propagates unchanged (Python would let the SDK's exception propagate out of the `await` the same way); everything else goes through Extract.

func SchemaFor

func SchemaFor[T any]() map[string]any

SchemaFor resolves the JSON-schema map the Go SDK harness consumes for T.

Resolution order:

  1. testdata/schemas/<reflect.TypeOf(T).Name()>.json, embedded above. This is the pydantic model_json_schema() of the identically named Python class.
  2. invopop reflection over the Go type, for types with no fixture (ad-hoc test structs, and any future destination whose Python counterpart is not in gen_schemas.py's MODELS list).

Why the fixture must win: the SDK runs REAL JSON-Schema validation on parsed output (harness/runner.go -> runSchemaValidation, santhosh-tekuri/jsonschema/v5) and retries the whole harness call when it fails. An invopop reflection marks every field required, renders pointer fields non-nullable and sets additionalProperties:false, so Python-valid model output would be rejected and the node would burn retries and drop findings.

Invopop reflector configuration (fallback path only):

  • ExpandedStruct: inline the root type's own properties at the top level so map["properties"] is populated for the SDK's DiagnoseOutputFailure.
  • DoNotReference=false (default): emit a $defs map for nested struct types.
  • Anonymous: suppress the auto-generated $id derived from the package path.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL