scopespy

package
v1.10.4 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package scopespy provides a recording store double for asserting that every store call made by the engine carries a non-zero scope.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BlockingStreamLLM

func BlockingStreamLLM() llm.Client

BlockingStreamLLM returns an llm.Client whose stream hangs in Next until ctx is cancelled, for testing StreamAgent's cancellation path.

func StaticLLM

func StaticLLM(reply string) llm.Client

StaticLLM returns an llm.Client that always answers with reply and never requests a tool, so a run terminates after one step.

func StaticStreamLLM

func StaticStreamLLM(reply string) llm.Client

StaticStreamLLM returns an llm.Client whose CompleteStream answers with a single chunk containing reply and then ends, so streamReAct completes a run without needing a real provider. Complete is implemented for interface completeness but the streaming tests that use this double never call it.

func ToolCallingLLM

func ToolCallingLLM(toolName string) llm.Client

ToolCallingLLM returns an llm.Client that requests toolName on its first Complete call and answers plainly on the next, so the react loop reaches CreateToolCall. Used with the synchronous RunAgent path only — CompleteStream is not implemented.

func ToolCallingStreamLLM

func ToolCallingStreamLLM(toolName string) llm.Client

ToolCallingStreamLLM returns an llm.Client whose stream requests toolName once and then answers plainly, so the streaming react loop reaches its tool-dispatch branch. Complete is implemented for interface completeness and is not what the streaming tests exercise.

Types

type Call

type Call struct {
	Method string
	Scope  cortex.Scope
	// SessionID is the session id a conversation call carried, when the
	// method takes one. Zero for every method that doesn't.
	SessionID id.SessionID
	// CtxErr is ctx.Err() at call time. A terminal write issued from an
	// already-cancelled ctx would otherwise fail before it reaches a real
	// store, so a non-nil CtxErr on a call that's supposed to persist a
	// cancel/failure outcome is the same bug shape this field exists to
	// catch even though the spy itself doesn't reject cancelled contexts.
	CtxErr error
}

Call is one recorded store invocation.

type FailingLLMStream

type FailingLLMStream struct{ Started chan struct{} }

FailingLLMStream's stream blocks in Next until the test cancels ctx, then returns a plain (non-io.EOF, non-ctx.Err()-derived) error — a stand-in for a real transport failure that happens to race with cancellation. Started is closed the instant Next is entered, so a test can confirm the react loop's ctx.Done() select case was NOT what routed it here (that case can only fire before Next is even called); the failure has to come from the err != nil branch below it instead.

func FailingStreamLLM

func FailingStreamLLM() (llm.Client, *FailingLLMStream)

FailingStreamLLM returns an llm.Client whose stream hangs in Next until ctx is cancelled and then fails with a plain error, for testing that a stream error arriving on an already-cancelled context still reaches failRun and persists.

func (*FailingLLMStream) Complete

func (f *FailingLLMStream) Complete(_ context.Context, _ *llm.Request) (*llm.Response, error)

func (*FailingLLMStream) CompleteStream

func (f *FailingLLMStream) CompleteStream(_ context.Context, _ *llm.Request) (llm.Stream, error)

type Spy

type Spy struct {
	store.Store
	// contains filtered or unexported fields
}

Spy implements store.Store by embedding it as a nil interface and overriding only what the react loop calls. Anything else panics, which is deliberate: an unrecorded call is a hole in the regression guard.

func New

func New() *Spy

func (*Spy) AdmitResumeLeased added in v1.10.0

AdmitResumeLeased records its call. The spy keeps no resume admissions.

func (*Spy) CallCount

func (s *Spy) CallCount() int

func (*Spy) Calls

func (s *Spy) Calls() []Call

func (*Spy) Checkpoints

func (s *Spy) Checkpoints() []*checkpoint.Checkpoint

Checkpoints returns the checkpoints the engine wrote, in order.

func (*Spy) ClaimContinuation added in v1.10.0

func (s *Spy) ClaimContinuation(ctx context.Context, runID id.AgentRunID, lease run.Lease) (*run.Run, error)

ClaimContinuation lets exactly one caller take a ready or reclaimable run, and refuses a half lease the way the stores do.

func (*Spy) ClaimContinuationLeased added in v1.10.0

func (s *Spy) ClaimContinuationLeased(ctx context.Context, runID id.AgentRunID, lease run.Lease, first *suspension.Progress) (*run.Run, error)

ClaimContinuationLeased is ClaimContinuation under a lease that must be set, keeping the first snapshot.

func (*Spy) ClaimSuspension

func (s *Spy) ClaimSuspension(ctx context.Context, runID id.AgentRunID) (*suspension.Suspension, error)

ClaimSuspension models the one property the real stores go to a transaction for: a suspension can only be claimed once. The second claimer gets ErrNotSuspended, exactly as it would against a run the first claimer already moved out of paused.

func (*Spy) ClaimSuspensionLeased added in v1.10.0

func (s *Spy) ClaimSuspensionLeased(ctx context.Context, claim suspension.LeasedClaim) (*suspension.Suspension, error)

ClaimSuspensionLeased claims the pinned pause once, takes the lease and keeps the first snapshot, with the real stores' refusals.

func (*Spy) CreateCheckpoint

func (s *Spy) CreateCheckpoint(ctx context.Context, cp *checkpoint.Checkpoint) error

CreateCheckpoint records the checkpoint itself, not just that the call happened: an approval suspension's whole point is what the row a human will read says.

func (*Spy) CreateRun

func (s *Spy) CreateRun(ctx context.Context, r *run.Run) error

CreateRun keeps the run so GetRun can serve it back. A resume reads the run it is continuing, and a spy that forgot every run it was handed could not exercise that path at all.

func (*Spy) CreateSession

func (s *Spy) CreateSession(ctx context.Context, _ *session.Session) error

CreateSession records the call and succeeds. resolveSession already generated the session's id before calling this, so there is nothing for the spy to mutate on the way back.

func (*Spy) CreateStep

func (s *Spy) CreateStep(ctx context.Context, step *run.Step) error

func (*Spy) CreateSuspension

func (s *Spy) CreateSuspension(ctx context.Context, susp *suspension.Suspension) error

CreateSuspension records the suspension itself, not just that the call happened: a suspend test's whole subject is what the row says.

It also refuses a second suspension for a run that already has one, which is the partial unique index every real backend carries. Without it a resume that forgot to delete the row it claimed would still pass here, and the collision would only show up in production.

func (*Spy) CreateToolCall

func (s *Spy) CreateToolCall(ctx context.Context, tc *run.ToolCall) error

CreateToolCall records the row itself: a resumed call's row is the audit trail entry a test here is actually asserting on.

func (*Spy) DeleteProgress added in v1.10.0

func (s *Spy) DeleteProgress(ctx context.Context, runID id.AgentRunID) error

DeleteProgress removes a snapshot saved at the exact same scope; a run with none is not an error.

func (*Spy) DeleteSuspension

func (s *Spy) DeleteSuspension(ctx context.Context, runID id.AgentRunID) error

DeleteSuspension drops the row, so a resumed run that suspends again writes a fresh one rather than colliding with its own stale one.

func (*Spy) FailCheckpointWrites

func (s *Spy) FailCheckpointWrites(err error)

FailCheckpointWrites makes every later CreateCheckpoint return err, so a test can drive the engine's "the checkpoint could not be written" branch without a real store.

func (*Spy) FailRunReadsAfter

func (s *Spy) FailRunReadsAfter(n int, err error)

FailRunReadsAfter lets the next n GetRun calls through and fails every one after them, so a test can break the read that follows a claim without breaking the reads that set the claim up.

func (*Spy) FailSuspensionWrites

func (s *Spy) FailSuspensionWrites(err error)

FailSuspensionWrites makes every later CreateSuspension return err, so a test can drive the engine's "the suspension could not be written" branch without a real store.

func (*Spy) Get

func (s *Spy) Get(ctx context.Context, agentID id.AgentID) (*agent.Config, error)

Get returns the same synthetic record as GetByName, with detached slices.

func (*Spy) GetByName

func (s *Spy) GetByName(ctx context.Context, name string) (*agent.Config, error)

GetByName returns a usable agent so RunAgent proceeds into the react loop. Returning an error here would end the run before any of the conversation calls we are actually trying to observe. PersonaRef, InlineSkills and InlineTraits are populated so BuildSystemPrompt reaches GetPersonaByName/GetSkillByName/GetTraitByName too — those calls carry scope the same as everything else the loop touches. MaxSteps is 2 so a tool-calling LLM double gets a second step to answer in after the tool call, without changing behavior for a non-tool-calling double, which breaks out of the loop after step one regardless of MaxSteps.

func (*Spy) GetCheckpoint

func (s *Spy) GetCheckpoint(ctx context.Context, cpID id.CheckpointID) (*checkpoint.Checkpoint, error)

GetSuspension serves back what CreateSuspension recorded. GetCheckpoint serves back what CreateCheckpoint recorded, including the state Resolve moved it to: a decision routed off a stale read is a decision applied twice.

func (*Spy) GetOverlayForAgentAt

func (s *Spy) GetOverlayForAgentAt(ctx context.Context, _ id.AgentID, _ cortex.Scope) (*prompt.Overlay, error)

GetOverlayForAgentAt records the scope of one rung of the ancestor walk BuildSystemPrompt makes, then reports no overlay. The spy models an agent with none, which is the ordinary case: the layering behavior itself is exercised against a real backend, since a fake that answers this call from a map proves nothing about which rows a store hands back for a given scope.

func (*Spy) GetPersonaByName

func (s *Spy) GetPersonaByName(ctx context.Context, name string) (*persona.Persona, error)

GetPersonaByName returns a usable persona so BuildSystemPrompt's identity injection has something to record scope on.

func (*Spy) GetProgress added in v1.10.0

func (s *Spy) GetProgress(ctx context.Context, runID id.AgentRunID) (*suspension.Progress, error)

GetProgress reads back a snapshot saved at the exact same scope.

func (*Spy) GetRun

func (s *Spy) GetRun(ctx context.Context, runID id.AgentRunID) (*run.Run, error)

GetRun returns a copy, the way a real store would. Handing back the same object the engine already holds would let a test pass on a run the engine never actually persisted.

func (*Spy) GetSkillByName

func (s *Spy) GetSkillByName(ctx context.Context, name string) (*skill.Skill, error)

GetSkillByName returns a usable skill so BuildSystemPrompt's skill injection has something to record scope on.

func (*Spy) GetSuspension

func (s *Spy) GetSuspension(ctx context.Context, runID id.AgentRunID) (*suspension.Suspension, error)

func (*Spy) GetTraitByName

func (s *Spy) GetTraitByName(ctx context.Context, name string) (*trait.Trait, error)

GetTraitByName returns a usable trait so BuildSystemPrompt's trait injection has something to record scope on.

func (*Spy) HoldLease added in v1.10.0

func (s *Spy) HoldLease(ctx context.Context, runID id.AgentRunID, lease run.Lease) error

HoldLease sets or extends the lease of a running run that nobody else holds.

func (*Spy) ListContinuableAcrossScopes added in v1.10.0

func (s *Spy) ListContinuableAcrossScopes(ctx context.Context, notifiedBefore time.Time, limit int) ([]*run.Run, error)

ListContinuableAcrossScopes deliberately reads every scope, as the sweeper does.

func (*Spy) ListLapsedAcrossScopes added in v1.10.0

func (s *Spy) ListLapsedAcrossScopes(ctx context.Context, now time.Time, limit int) ([]*run.Run, error)

ListLapsedAcrossScopes deliberately reads every scope, as the sweeper does.

func (*Spy) ListPending

func (s *Spy) ListPending(ctx context.Context, filter *checkpoint.ListFilter) ([]*checkpoint.Checkpoint, error)

ListPending serves the pending rows back, filtered by run the way the real stores filter them. A cancelled run resolves whatever it left in the queue, and a spy that returned nothing could not tell that apart from a cancel that skipped the queue entirely.

func (*Spy) ListPendingAcrossScopes added in v1.10.0

func (s *Spy) ListPendingAcrossScopes(ctx context.Context, after id.CheckpointID, limit int) ([]*checkpoint.Checkpoint, error)

ListPendingAcrossScopes deliberately reads every scope, as the sweeper does, in id order.

func (*Spy) ListSessions

func (s *Spy) ListSessions(ctx context.Context, _ *session.ListFilter) ([]*session.Session, error)

ListSessions returns no existing sessions, so resolveSession's default-session lookup falls through to CreateSession below — the same lazy-create path a fresh agent takes in a real store.

func (*Spy) ListSteps

func (s *Spy) ListSteps(ctx context.Context, runID id.AgentRunID) ([]*run.Step, error)

ListSteps is what a resume uses to find the step its pending calls came from, so the tool call rows it writes land on the step that made them.

func (*Spy) LoadConversation

func (s *Spy) LoadConversation(ctx context.Context, _ id.AgentID, sessionID id.SessionID, _ int) ([]memory.Message, error)

func (*Spy) MarkNotified added in v1.10.0

func (s *Spy) MarkNotified(ctx context.Context, runID id.AgentRunID, at time.Time) error

MarkNotified stamps a ready or reclaimable run.

func (*Spy) MarkReady added in v1.10.0

func (s *Spy) MarkReady(ctx context.Context, runID id.AgentRunID, ready run.Readiness) (*run.Run, error)

MarkReady applies the real stores' rules to the recorded run and suspension: only a paused run with a live suspension becomes ready, and the suspension loses its deadline.

func (*Spy) MarkReclaimable added in v1.10.0

func (s *Spy) MarkReclaimable(ctx context.Context, runID id.AgentRunID, now time.Time, owner string) (*run.Run, error)

MarkReclaimable takes a running run from a lapsed lease, or from its owner when the owner hands it off.

func (*Spy) Resolve

func (s *Spy) Resolve(ctx context.Context, cpID id.CheckpointID, decision checkpoint.Decision) error

Resolve records the decision on the stored row, the way a real store does. A spy that only counted the call could not tell a checkpoint resolved once from one resolved twice.

func (*Spy) Runs

func (s *Spy) Runs() []run.Run

Runs returns every run the engine wrote, so a test whose RunAgent returned an error rather than a run can still ask what state that run ended in. Order is undefined: the map behind it has none.

func (*Spy) SaveConversation

func (s *Spy) SaveConversation(ctx context.Context, _ id.AgentID, sessionID id.SessionID, _ []memory.Message) error

func (*Spy) SaveProgress added in v1.10.0

func (s *Spy) SaveProgress(ctx context.Context, p *suspension.Progress) error

SaveProgress applies the real stores' fence: only the owner of a running run's lease may save its snapshot, and an empty owner never matches. Like the real stores it sets p.Scope and p.UpdatedAt to what it kept.

func (*Spy) StartResumeLeased added in v1.10.0

func (s *Spy) StartResumeLeased(ctx context.Context, _ id.AgentRunID, _ id.SuspensionID, _ string, _ run.Lease, _ *suspension.Progress) (suspension.ResumeStart, error)

StartResumeLeased records its call. The spy keeps no resume admissions.

func (*Spy) Steps

func (s *Spy) Steps() []run.Step

Steps returns the steps the engine wrote, in order.

func (*Spy) Suspensions

func (s *Spy) Suspensions() []*suspension.Suspension

Suspensions returns the suspensions the engine wrote, in order.

func (*Spy) ToolCalls

func (s *Spy) ToolCalls() []run.ToolCall

ToolCalls returns the tool call rows the engine wrote, in order.

func (*Spy) UpdateRun

func (s *Spy) UpdateRun(ctx context.Context, r *run.Run) error

UpdateRun keeps what the real stores keep: it never writes the lease, the readiness, the reclaim count, the notification time or the reply route, and it clears the lease when the run leaves running.

Jump to

Keyboard shortcuts

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