diff --git a/SECURITY.md b/SECURITY.md index e2002ac..097ca0c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,12 @@ # Security Policy +## Threat Model + +- The trust boundary is a 0600 socket inside a 0700 directory. +- `SO_PEERCRED` is advisory logging only, not access control. +- Any process running as the same uid is trusted. +- `--as` is a name-collision guard, not authentication. + ## Supported Versions Tether is currently in active development. Security updates are provided for the latest release branch. diff --git a/cmd/tether/cmd_doctor.go b/cmd/tether/cmd_doctor.go index 816050c..0f9863c 100644 --- a/cmd/tether/cmd_doctor.go +++ b/cmd/tether/cmd_doctor.go @@ -52,7 +52,7 @@ func newDoctorCmd() *cobra.Command { cmd := &cobra.Command{ Use: "doctor", - Short: "Check the daemon, this workspace, and every agent's wake tier", + Short: "Check the daemon, this workspace, and every agent here", Long: doctorLong, Example: " tether doctor\n" + " tether doctor --json", diff --git a/cmd/tether/cmd_explain.go b/cmd/tether/cmd_explain.go index 7035234..cec6e5f 100644 --- a/cmd/tether/cmd_explain.go +++ b/cmd/tether/cmd_explain.go @@ -11,8 +11,8 @@ import ( const explainLong = `Explain one agent's computed state and how much mail is waiting for it. -With no argument this reports on you (--as, or $TETHER_NAME). Pass an address to -inspect somebody else before sending them work. +With no argument this reports on you (--as, or whatever this session already +registered). Pass an address to inspect somebody else before sending them work. state/source/seen/detail are computed fresh on every call, never stored: seen is how old the evidence behind state is, and detail says what that evidence diff --git a/cmd/tether/cmd_wait.go b/cmd/tether/cmd_wait.go index 5ad329e..d942aa4 100644 --- a/cmd/tether/cmd_wait.go +++ b/cmd/tether/cmd_wait.go @@ -26,7 +26,7 @@ on it: if tether wait --timeout 2m; then tether inbox; fi This is the polling-free way to idle: agents whose harness the daemon cannot -wake (the "universal" tier) should sit in wait rather than calling inbox in a loop.` +wake should sit in wait rather than calling inbox in a loop.` type waitOptions struct { identityFlags diff --git a/cmd/tether/identity.go b/cmd/tether/identity.go index 294a34c..76ae4c9 100644 --- a/cmd/tether/identity.go +++ b/cmd/tether/identity.go @@ -10,8 +10,7 @@ import ( "github.com/praneethravuri/tether/internal/wsname" ) -// Harness identifiers reported at registration time. They are stable strings: -// the daemon maps them to a notifier and a wake tier. +// Harness identifiers reported at registration time. const ( harnessClaudeCode = "claude-code" harnessGeminiCLI = "gemini-cli" diff --git a/cmd/tether/stale_help_test.go b/cmd/tether/stale_help_test.go new file mode 100644 index 0000000..4ff214d --- /dev/null +++ b/cmd/tether/stale_help_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "testing" + + "github.com/spf13/cobra" +) + +// Guards help text against advertising features the store no longer has: +// notifier/tier columns are dropped in migrateV1ToV2, and nothing reads +// $TETHER_NAME. +func TestHelpDoesNotAdvertiseRemovedFeatures(t *testing.T) { + cases := []struct { + name string + newCmd func() *cobra.Command + unwanted string + }{ + // doctor's Short only surfaces in the parent listing, not its own --help. + {"doctor wake tier", newRootCmd, "wake tier"}, + {"explain TETHER_NAME", newExplainCmd, "TETHER_NAME"}, + {"wait universal tier", newWaitCmd, "universal"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + r := mustRun(t, c.newCmd(), "", "--help") + requireNotContains(t, r.stdout, c.unwanted, "help output") + }) + } +} diff --git a/skills/tether/SKILL.md b/skills/tether/SKILL.md index d4335ce..344b7a2 100644 --- a/skills/tether/SKILL.md +++ b/skills/tether/SKILL.md @@ -66,6 +66,10 @@ triage hints, not enforced. Use `--reply-to ` when answering a question. Send to `'*'` or `all` (quoted, so the shell doesn't glob it) to reach everyone else in the workspace. +One agent's pending mail is capped at 500 messages. Past that, `note`s are +evicted first — a `handoff`/`question`/`answer` only starts dropping once +every pending `note` is gone. + ## Read mail ```sh @@ -76,6 +80,15 @@ Shows pending messages and clears them in the same step — reading IS acknowledging, there's no separate ack command. Use `--peek` to look without clearing. +## Recovering messages you already read + +```sh +tether inbox --replay +``` + +Shows what an earlier `inbox` drain already delivered, in case the context +that came with it got lost. Mutually exclusive with `--peek`. + ## Wait for a reply ```sh @@ -86,6 +99,19 @@ Blocks until mail arrives (exit 0) or the timeout elapses (exit 4). Prefer this over polling `inbox` in a loop — it wakes the instant a message is sent instead of on some poll interval. +Nothing wakes an agent that isn't sitting in `wait`. Call it as a checkpoint: +after each subtask, before a long build, and before declaring work done. + +To ask another agent something and block for the reply: + +```sh +tether send backend --kind question "does /orders paginate yet?" +tether wait --timeout 5m +tether inbox +``` + +The answer arrives with `--reply-to` pointing at the question's message id. + ## Exit codes | Code | Meaning |