jobsclient

package
v1.0.0-alpha.13 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package jobsclient is the runtime-side client to the openotters daemon's async-jobs API. It exists so an agent's runtime can submit long-running BIN jobs against its own spawn env, check on them later, or block until they complete — without pinning the agent's turn-loop goroutine for the duration.

Configuration comes from two env vars planted in the spawn env by the executor (see agentfile/executor/env.go's BuildLockedEnv):

  • OTTERSD_URL where to dial the daemon. unix://<path> for the system executor; an http:// URL for docker (the executor sets host.docker.internal so the same value resolves on macOS Docker Desktop and Linux Docker via ExtraHosts).
  • OTTERS_AGENT_TOKEN the JWT minted by the daemon at CreateAgent. Attached as `Authorization: Bearer …` on every RPC. Empty token → constructor returns nil (caller treats that as "the daemon callback path isn't wired").

The client lazy-dials on first use so a runtime that never invokes a job tool pays nothing for the dependency.

Index

Constants

View Source
const (
	EnvDaemonURL  = "OTTERSD_URL"
	EnvAgentToken = "OTTERS_AGENT_TOKEN"
)

Env names — exported so tests / callers can override programmatically without re-coding the convention.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps the daemon's RuntimeClient with bearer-token injection and lazy dialing. Safe for concurrent use; the underlying gRPC connection multiplexes RPCs.

func New

func New(cfg Config) *Client

New constructs a client without dialing. cfg.URL + cfg.Token both required — empty values yield an immediate error on first call.

func (*Client) CancelJob

func (c *Client) CancelJob(ctx context.Context, jobID string) error

CancelJob requests immediate cancellation. The job transitions to `cancelled` once the daemon's pool reaps it (~250 ms typical).

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying gRPC connection. Safe to call multiple times; first call wins.

func (*Client) FollowJob

func (c *Client) FollowJob(ctx context.Context, jobID string) (*JobView, error)

FollowJob is the read-only counterpart of WatchJob: it streams the job's state until terminal but does NOT cancel the underlying job when the caller's context is cancelled. Use it when the caller is an *observer* of work owned by someone else — `job_watch` from the agent's tool set, for instance, where an interrupted agent turn shouldn't yank the BIN it was monitoring.

On ctx-cancel the stream Recv returns with ctx.Err and we return (lastSeen, err) — the caller gets whatever snapshot landed before the disconnect. The daemon keeps running the job.

Implementation parity with WatchJob is intentional: only the cancel-on-disconnect goroutine is omitted. If WatchJob's stream loop evolves, this method should track it (extract a shared recvLoop helper if it grows).

func (*Client) GetJob

func (c *Client) GetJob(ctx context.Context, jobID string) (*JobView, error)

GetJob is the non-blocking snapshot. Useful for "did it finish yet" polling without holding the agent's turn open.

func (*Client) ListJobs

func (c *Client) ListJobs(ctx context.Context, opts ListJobsOpts) ([]*JobView, error)

ListJobs returns the agent's async-job snapshots, filtered by status (optional) and label selector (optional). Used by the agent's `job_list` tool to surface "my recent jobs in this session" without the agent having to thread its own UUID through the call. Order is daemon-side (created_at DESC); the caller trims to the desired window.

func (*Client) SubmitJob

func (c *Client) SubmitJob(
	ctx context.Context, bin string, args []string, stdin string, labels map[string]string,
) (string, error)

SubmitJob dispatches a BIN against the agent's spawn env. The daemon binds agent_ref to the JWT's claim, so the request-side value is ignored — agents can't submit jobs on behalf of another agent regardless of what they put in the request.

func (*Client) WatchJob

func (c *Client) WatchJob(ctx context.Context, jobID string) (*JobView, error)

WatchJob blocks until the job reaches a terminal status, then returns the final snapshot. Forwards ctx cancellation to a CancelAsyncJob call so an interrupted agent turn doesn't leave orphaned jobs running on the daemon.

The daemon's WatchAsyncJob is a server-stream that emits the current state immediately and on every material change; this helper drains until terminal.

type Config

type Config struct {
	URL   string // OTTERSD_URL — unix://<path> or http://host:port
	Token string // OTTERS_AGENT_TOKEN — JWT presented as Bearer
}

Config holds the dial inputs. Use FromEnv to populate from the spawn env; the explicit struct exists for testing.

func FromEnv

func FromEnv() (Config, bool)

FromEnv reads config from the runtime's process environment. Returns (zero, false) when either var is missing — the caller treats this as "no daemon callback path configured" and skips registering the job tools.

type JobView

type JobView struct {
	JobID    string `json:"job_id"`
	Status   string `json:"status"` // pending|running|done|error|cancelled|orphaned
	ExitCode int    `json:"exit_code"`
	Stdout   string `json:"stdout"`
	Stderr   string `json:"stderr"`
	Error    string `json:"error,omitempty"`
}

JobView is the agent-facing snapshot of an async job. Mirrors the proto's AsyncJob but trims fields the agent doesn't need (handle, labels, started_at, …) so the tool's JSON Schema stays small in the model's prompt.

func (*JobView) IsTerminal

func (v *JobView) IsTerminal() bool

IsTerminal reports whether the job has reached a final status. The daemon's status string is the source of truth; this helper just names the four values so call sites read better.

type ListJobsOpts

type ListJobsOpts struct {
	Status string
	Labels map[string]string
}

ListJobsOpts narrows the agent-side list query. AgentID is NOT part of the options: the daemon pins the result set to the JWT's bound agent regardless of what the request carries, so the runtime never has to know or guess its own UUID. Labels filter is AND-merged: every key/value must match.

Jump to

Keyboard shortcuts

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