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 ¶
- func BlockingStreamLLM() llm.Client
- func StaticLLM(reply string) llm.Client
- func StaticStreamLLM(reply string) llm.Client
- func ToolCallingLLM(toolName string) llm.Client
- func ToolCallingStreamLLM(toolName string) llm.Client
- type Call
- type FailingLLMStream
- type Spy
- func (s *Spy) AdmitResumeLeased(ctx context.Context, _ suspension.ResumeAdmissionRequest, _ run.Lease, ...) (suspension.ResumeAdmission, error)
- func (s *Spy) CallCount() int
- func (s *Spy) Calls() []Call
- func (s *Spy) Checkpoints() []*checkpoint.Checkpoint
- func (s *Spy) ClaimContinuation(ctx context.Context, runID id.AgentRunID, lease run.Lease) (*run.Run, error)
- func (s *Spy) ClaimContinuationLeased(ctx context.Context, runID id.AgentRunID, lease run.Lease, ...) (*run.Run, error)
- func (s *Spy) ClaimSuspension(ctx context.Context, runID id.AgentRunID) (*suspension.Suspension, error)
- func (s *Spy) ClaimSuspensionLeased(ctx context.Context, claim suspension.LeasedClaim) (*suspension.Suspension, error)
- func (s *Spy) CreateCheckpoint(ctx context.Context, cp *checkpoint.Checkpoint) error
- func (s *Spy) CreateRun(ctx context.Context, r *run.Run) error
- func (s *Spy) CreateSession(ctx context.Context, _ *session.Session) error
- func (s *Spy) CreateStep(ctx context.Context, step *run.Step) error
- func (s *Spy) CreateSuspension(ctx context.Context, susp *suspension.Suspension) error
- func (s *Spy) CreateToolCall(ctx context.Context, tc *run.ToolCall) error
- func (s *Spy) DeleteProgress(ctx context.Context, runID id.AgentRunID) error
- func (s *Spy) DeleteSuspension(ctx context.Context, runID id.AgentRunID) error
- func (s *Spy) FailCheckpointWrites(err error)
- func (s *Spy) FailRunReadsAfter(n int, err error)
- func (s *Spy) FailSuspensionWrites(err error)
- func (s *Spy) Get(ctx context.Context, agentID id.AgentID) (*agent.Config, error)
- func (s *Spy) GetByName(ctx context.Context, name string) (*agent.Config, error)
- func (s *Spy) GetCheckpoint(ctx context.Context, cpID id.CheckpointID) (*checkpoint.Checkpoint, error)
- func (s *Spy) GetOverlayForAgentAt(ctx context.Context, _ id.AgentID, _ cortex.Scope) (*prompt.Overlay, error)
- func (s *Spy) GetPersonaByName(ctx context.Context, name string) (*persona.Persona, error)
- func (s *Spy) GetProgress(ctx context.Context, runID id.AgentRunID) (*suspension.Progress, error)
- func (s *Spy) GetRun(ctx context.Context, runID id.AgentRunID) (*run.Run, error)
- func (s *Spy) GetSkillByName(ctx context.Context, name string) (*skill.Skill, error)
- func (s *Spy) GetSuspension(ctx context.Context, runID id.AgentRunID) (*suspension.Suspension, error)
- func (s *Spy) GetTraitByName(ctx context.Context, name string) (*trait.Trait, error)
- func (s *Spy) HoldLease(ctx context.Context, runID id.AgentRunID, lease run.Lease) error
- func (s *Spy) ListContinuableAcrossScopes(ctx context.Context, notifiedBefore time.Time, limit int) ([]*run.Run, error)
- func (s *Spy) ListLapsedAcrossScopes(ctx context.Context, now time.Time, limit int) ([]*run.Run, error)
- func (s *Spy) ListPending(ctx context.Context, filter *checkpoint.ListFilter) ([]*checkpoint.Checkpoint, error)
- func (s *Spy) ListPendingAcrossScopes(ctx context.Context, after id.CheckpointID, limit int) ([]*checkpoint.Checkpoint, error)
- func (s *Spy) ListSessions(ctx context.Context, _ *session.ListFilter) ([]*session.Session, error)
- func (s *Spy) ListSteps(ctx context.Context, runID id.AgentRunID) ([]*run.Step, error)
- func (s *Spy) LoadConversation(ctx context.Context, _ id.AgentID, sessionID id.SessionID, _ int) ([]memory.Message, error)
- func (s *Spy) MarkNotified(ctx context.Context, runID id.AgentRunID, at time.Time) error
- func (s *Spy) MarkReady(ctx context.Context, runID id.AgentRunID, ready run.Readiness) (*run.Run, error)
- func (s *Spy) MarkReclaimable(ctx context.Context, runID id.AgentRunID, now time.Time, owner string) (*run.Run, error)
- func (s *Spy) Resolve(ctx context.Context, cpID id.CheckpointID, decision checkpoint.Decision) error
- func (s *Spy) Runs() []run.Run
- func (s *Spy) SaveConversation(ctx context.Context, _ id.AgentID, sessionID id.SessionID, _ []memory.Message) error
- func (s *Spy) SaveProgress(ctx context.Context, p *suspension.Progress) error
- func (s *Spy) StartResumeLeased(ctx context.Context, _ id.AgentRunID, _ id.SuspensionID, _ string, _ run.Lease, ...) (suspension.ResumeStart, error)
- func (s *Spy) Steps() []run.Step
- func (s *Spy) Suspensions() []*suspension.Suspension
- func (s *Spy) ToolCalls() []run.ToolCall
- func (s *Spy) UpdateRun(ctx context.Context, r *run.Run) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BlockingStreamLLM ¶
BlockingStreamLLM returns an llm.Client whose stream hangs in Next until ctx is cancelled, for testing StreamAgent's cancellation path.
func StaticLLM ¶
StaticLLM returns an llm.Client that always answers with reply and never requests a tool, so a run terminates after one step.
func StaticStreamLLM ¶
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 ¶
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 ¶
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) CompleteStream ¶
type Spy ¶
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 (*Spy) AdmitResumeLeased ¶ added in v1.10.0
func (s *Spy) AdmitResumeLeased(ctx context.Context, _ suspension.ResumeAdmissionRequest, _ run.Lease, _ *suspension.Progress) (suspension.ResumeAdmission, error)
AdmitResumeLeased records its call. The spy keeps no resume admissions.
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 ¶
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 ¶
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) 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 ¶
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
DeleteProgress removes a snapshot saved at the exact same scope; a run with none is not an error.
func (*Spy) DeleteSuspension ¶
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 ¶
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 ¶
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 ¶
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) GetByName ¶
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 ¶
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 ¶
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 ¶
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 ¶
GetTraitByName returns a usable trait so BuildSystemPrompt's trait injection has something to record scope on.
func (*Spy) HoldLease ¶ added in v1.10.0
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 ¶
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 ¶
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 (*Spy) MarkNotified ¶ added in v1.10.0
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 ¶
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 (*Spy) SaveProgress ¶ added in v1.10.0
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) Suspensions ¶
func (s *Spy) Suspensions() []*suspension.Suspension
Suspensions returns the suspensions the engine wrote, in order.